943

I have the following .txt file:

Marco
Paolo
Antonio

I want to read it line-by-line, and for each line I want to assign a .txt line value to a variable. Supposing my variable is $name, the flow is:

  • Read first line from file
  • Assign $name = "Marco"
  • Do some tasks with $name
  • Read second line from file
  • Assign $name = "Paolo"
Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
Marco
  • 10,283
  • 4
  • 23
  • 22
  • 4
    Can those questions maybe be merged somehow? Both have some really good answers that highlight different aspects of the problem, the bad answers have in-depth explanations in the comments what's bad about them, and as of now you cannot really get a whole overview on what to consider, from the answers of one single question from the pair. It would be helpful to have all of it in one spot, rather than splotted over 2 pages. – Egor Hans Nov 12 '17 at 16:11

10 Answers10

1688

The following reads a file passed as an argument line by line:

while IFS= read -r line; do
    echo "Text read from file: $line"
done < my_filename.txt

This is the standard form for reading lines from a file in a loop. Explanation:

  • IFS= (or IFS='') prevents leading/trailing whitespace from being trimmed.
  • -r prevents backslash escapes from being interpreted.

Or you can put it in a bash file helper script, example contents:

#!/bin/bash
while IFS= read -r line; do
    echo "Text read from file: $line"
done < "$1"

If the above is saved to a script with filename readfile, it can be run as follows:

chmod +x readfile
./readfile filename.txt

If the file isn’t a standard POSIX text file (= not terminated by a newline character), the loop can be modified to handle trailing partial lines:

while IFS= read -r line || [[ -n "$line" ]]; do
    echo "Text read from file: $line"
done < "$1"

Here, || [[ -n $line ]] prevents the last line from being ignored if it doesn't end with a \n (since read returns a non-zero exit code when it encounters EOF).

If the commands inside the loop also read from standard input, the file descriptor used by read can be chanced to something else (avoid the standard file descriptors), e.g.:

while IFS= read -r -u3 line; do
    echo "Text read from file: $line"
done 3< "$1"

(Non-Bash shells might not know read -u3; use read <&3 instead.)

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
cppcoder
  • 22,227
  • 6
  • 56
  • 81
  • 34
    There is a caveat with this method. If anything inside the while loop is interactive (e.g. reads from stdin), then it will take its input from $1. You will not be given a chance to enter data manually. – carpie Jan 16 '14 at 16:25
  • 16
    Of note - some commands break (as in, they break the loop) this. For example, `ssh` without the `-n` flag will effectively cause you to escape the loop. There's probably a good reason for this, but it took my a while to nail down what was causing my code to fail before I discovered this. – Alex May 20 '14 at 21:22
  • 1
    @mklement0 Wish I could upvote your comment twice – you just saved my day :) Confirmed: works perfectly! (I'm referring to the `IFS` hint) – Izzy Nov 13 '14 at 19:07
  • This doesnt work any more - if the file contains '*' characters, they are interpolated by the shell (with the files in the $PWD). – donatello Nov 20 '15 at 10:46
  • 8
    as a one-liner: while IFS='' read -r line || [[ -n "$line" ]]; do echo "$line"; done < filename – Jo Jo Dec 02 '15 at 23:20
  • The problem I have with this is that the `while..done` can get VERY big. At that point figuring out what the read in the while loop is actually reading can be a problem when you come back to the code months or years later. It would be better if you could use a syntax that has the filename attached to the 'while' keyword itself. My first though being... `< filename while read ....` But that that produces a syntax error. Best solution seems to have the shell open a file with to a file descriptor (number). But then you also have to remember close the file at the end. – anthony Feb 17 '16 at 04:33
  • 1
    Not sure what is the profit of `[[ ]]` over `[ ]` here – Grief Mar 24 '16 at 23:34
  • Doesn't seem a good solution for TSV files, as it translates tabulations into single spaces. – Skippy le Grand Gourou Apr 11 '16 at 12:33
  • @carpie Thanks! You make a good point, I had to do "cat /dev/null | mplayer ..." because mplayer would get the filenames piped as interactive input. I assume that's the case with any interactive command, so the cat /dev/null thing should help there -- thank you for helping figure that out :) – Jérémie Jul 06 '16 at 06:50
  • 1
    I almost want to vote against this method. It has serious issues. When you perform nested calls to other scripts within the loop, the position in the file being read shifts. I couldn't figure out why - without calling `ffmpeg` it worked. with calling it in a nested script it didn't. Probably a bash bug-feature. – Ondra Žižka Dec 10 '16 at 14:43
  • 12
    @OndraŽižka, that's caused by `ffmpeg` consuming stdin. Add ` – Charles Duffy Dec 14 '16 at 16:11
  • 12
    *grumble* re: advising a `.sh` extension. Executables on UNIX don't typically have extensions at all (you don't run `ls.elf`), and having a bash shebang (and bash-only tooling such as `[[ ]]`) and an extension implying POSIX sh compatibility is internally contradictory. – Charles Duffy May 21 '17 at 21:32
  • @mklement0 I still didn't get the logic behind `IFS=''`? How does it work that way? Its explanation is as cryptic as the code. – John Strood Feb 12 '18 at 13:35
  • 4
    @JohnStrood:[`IFS` (Internal Field Separator)](https://www.gnu.org/software/bash/manual/bashref.html#Word-Splitting) is a special variable that determines how `read` splits each line into words (fields). `IFS=''` - i.e., an assignment that sets `IFS` to the empty string - deactivates word splitting as well as trimming of leading and trailing whitespace to ensure that the entire line is returned as-is. `IFS='' read ...` scopes the `IFS` value change to the `read` command only. For an overview of techniques for setting `IFS`, see [this answer](https://stackoverflow.com/a/29493579/45375) of mine. – mklement0 Feb 12 '18 at 14:23
  • 1
    If you're on Linux and you copy/paste the answer be sure to run `sed -i -e 's/\r$//' the_script_file.sh` per https://askubuntu.com/a/305001/340686 – Kyle Bridenstine Mar 24 '20 at 20:21
  • So if you omit the IFS='' part, the read will treat spaces the same as line-breaks? Am I understanding that right? – Eliezer Miron Jun 07 '23 at 18:16
346

I encourage you to use the -r flag for read which stands for:

-r  Do not treat a backslash character in any special way. Consider each
    backslash to be part of the input line.

I am citing from man 1 read.

Another thing is to take a filename as an argument.

Here is updated code:

#!/usr/bin/bash
filename="$1"
while read -r line; do
    name="$line"
    echo "Name read from file - $name"
done < "$filename"
electronix384128
  • 6,625
  • 11
  • 45
  • 67
Grzegorz Wierzowiecki
  • 10,545
  • 9
  • 50
  • 88
  • 7
    Trims leading and trailing space from the line – barfuin Sep 21 '14 at 13:25
  • @Thomas and what happens to the spaces in the middle? Hint: Unwanted attempted command execution. – kmarsh Feb 23 '16 at 21:42
  • 1
    This worked for me, in contrast to the accepted answer. – Neurotransmitter Jun 30 '16 at 14:15
  • 3
    @TranslucentCloud, if this worked and the accepted answer didn't, I suspect that your shell was `sh`, not `bash`; the extended test command used in the `|| [[ -n "$line" ]]` syntax in the accepted answer is a bashism. That said, that syntax actually has pertinent meaning: It causes the loop to continue for the last line in the input file even if it doesn't have a newline. If you wanted to do that in a POSIX-compliant way, you'd want `|| [ -n "$line" ]`, using `[` rather than `[[`. – Charles Duffy Dec 14 '16 at 17:25
  • 3
    That said, this *does* still need to be modified to set `IFS=` for the `read` to prevent trimming whitespace. – Charles Duffy Dec 14 '16 at 17:28
  • This does not work if there are more than two lines in the file read. – tauseef_CuriousGuy May 03 '18 at 13:30
  • @tauseef_CuriousGuy, ...huh? See it working perfectly well with four lines of input at https://ideone.com/MdPSAk – Charles Duffy Nov 30 '18 at 03:03
160

Using the following Bash template should allow you to read one value at a time from a file and process it.

while read name; do
    # Do what you want to $name
done < filename
electronix384128
  • 6,625
  • 11
  • 45
  • 67
OneWinged
  • 1,633
  • 1
  • 9
  • 3
  • 20
    as a one-liner: while read name; do echo ${name}; done < filename – Jo Jo Dec 02 '15 at 23:17
  • I liked @Gert's answer because it works as a one-liner, but this one is more succinct. This example worked. `while read line; do echo $line; done < filename` – Calculus Knight Jun 23 '16 at 15:53
  • 6
    @CalculusKnight, it only "worked" because you didn't use sufficiently interesting data to test with. Try content with backslashes, or having a line that contains only `*`. – Charles Duffy Jun 28 '16 at 23:42
  • @CharlesDuffy it may well work for his purposes, and the data he has. There is no reason to cover every hypothetical edge case if they literally never come up. However, obviously it is good to know of the edge cases you refer to. – Matthias Dec 14 '16 at 11:58
  • 8
    @Matthias, assumptions that eventually turn out to be false are one of the largest sources of bugs, both security-impacting and otherwise. The largest data loss event I ever saw was due to a scenario someone assumed would "literally never come up" -- a buffer overflow dumping random memory into a buffer used to name files, causing a script that made assumptions about which names could possibly ever occur to have very, *very* unfortunate behavior. – Charles Duffy Dec 14 '16 at 15:59
  • 3
    @Matthias, ...the ops team at that employer were veterans in the field, with many years of experience before any one of them made a mistake that (by relying on names matching `[0-9a-f]{24}`) destroyed our customer billing backups -- but if the place such a mistake has impact has high enough cost, it's worth protecting against even if the scenario is once-in-a-decade. And the best way to protect against mistakes when it matters is to follow best practices even when it you don't know it'll matter. Code gets copied/pasted and reused in places its authors don't expect. – Charles Duffy Dec 14 '16 at 16:03
  • 5
    @Matthias, ...and that's **especially** true here, since code samples shown at StackOverflow are intended to be used as teaching tools, for folks to reuse the patterns in their own work! – Charles Duffy Dec 14 '16 at 16:06
  • 3
    @Matthias, ...if you were writing Java or C or Python, would you add in extra function calls you didn't expect to have any effect, because the scenario where they *would* have an effect and break your code is unlikely? When you don't quote your expansions in bash, you're literally asking the shell to perform globbing and string-splitting. When you don't pass `-r` to read, you're literally asking the shell to process backslash-escapes -- extra steps. If you want only a specific set of behaviors, you shouldn't be asking for more than those behaviors -- even by omission. – Charles Duffy Dec 14 '16 at 16:07
  • yeah after I commented I saw your comment on the question below. You do have a point. But so do I. For example, my purpose for using this was writing bash code directly in the terminal for some scripting work. Absolutely no need to include a `-r`, `IFS=''` etc. in that case. It was mostly your wording which irked me ("sufficiently interesting data"), in general you should only devise your code for the data you expect (otherwise you waste developer time), but of course, sensible precautions which are not hard to add (like `-r` and quotes) I would say are a good idea. – Matthias Dec 15 '16 at 11:26
  • I think I might try using `-r` everywhere just in case now :P – Matthias Dec 15 '16 at 11:26
  • 6
    @Matthias, I utterly disagree with the claim that "you should only devise your code for data you expect". Unexpected cases are where your bugs are, where your security vulnerabilities are -- handling them is the difference between slapdash code and robust code. Granted, that handling doesn't need to be fancy -- it can just be "exit with an error" -- but if you have no handling at all, then your behavior in unexpected cases is undefined. – Charles Duffy Jun 08 '17 at 20:26
  • 1
    I think we agree, but are only miscommunicating here. For example, those cases you speak of, the unexpected ones and the bug producing, are something I would always want to handle (if there's any chance they could happen). That and of course, depending on the consequences of them happening. For example, no sense spending months programming handling for some edge cases, where if they were to happen, only a minor log glitch would occur that could be fixed very quickly. – Matthias Jun 11 '17 at 12:14
  • In other words, do expect people to try and exploit your input interfaces. Bug producing cases shouldn't be unexpected if they come from interactive input - they're perfectly allowed to if you have a machine that produces your input, because as long as that machine has no bugs itself, it's infallible. – Egor Hans Nov 12 '17 at 16:21
97
#! /bin/bash
cat filename | while read LINE; do
    echo $LINE
done
electronix384128
  • 6,625
  • 11
  • 45
  • 67
Gert
  • 1,075
  • 7
  • 2
  • 8
    Nothing against the other answers, maybe they are more sofisticated, but I upvote this answer because it's simple, readable and is enough for what I need. Note that, for it to work, the text file to be read must end with a blank line (i.e. one needs to press `Enter` after the last line), otherwise the last line will be ignored. At least that is what happened to me. – Antônio Medeiros Feb 16 '16 at 13:32
  • 14
    Useless use of cat, shurely ? – Brian Agnew Apr 08 '16 at 11:42
  • 5
    And the quoting is broken; and you should not use uppercase variable names because those are reserved for system use. – tripleee Jun 07 '16 at 11:35
  • 2
    @AntonioViniciusMenezesMedei, bash is full of caveats -- it's easy to have something that "works" in a trivial case but breaks whenever you use it somewhere interesting. In this case, it'll expand globs (taking a line with `*` and writing a line with a list of filenames), string-split and rejoin whitespace (changing tabs to spaces, for instance), and eliminate backslashes in your input. – Charles Duffy Jun 28 '16 at 23:44
  • 8
    @AntonioViniciusMenezesMedei, ...moreover, I've seen folks sustain financial losses because they assumed these caveats would never matter to them; failed to learn good practices; and then followed the habits they were used to when writing scripts that managed backups of critical billing data. Learning to do things right is important. – Charles Duffy Jun 28 '16 at 23:46
  • 2
    Caveats aside, the form `ssh "lsusb" | while read ln; do echo $ln; done` hinted at here is quite useful. – duanev Aug 02 '16 at 18:12
  • this is also portable across all POSIX-compliant Bourne shells (doesn't require bash, /bin/sh works too, in case you are on a system without bash (like default BSD, AIX, IRIX, HPUX installations, for example)) – cowbert Jan 11 '17 at 15:07
  • @cowbert, "portable" in that it has the same bugs on all of them. Why wouldn't you want to use something that's *correct* everywhere instead? – Charles Duffy Jun 08 '17 at 20:24
  • 7
    Another problem here is that the pipe opens a new subshell, i.e. all variables set inside the loop can't be read after the loop finished. – mxmlnkn Sep 10 '17 at 19:18
  • @CharlesDuffy you are absolutely right. Each line goes into a variable, and a variables really mess up asterisk (*) and other special characters. I had lines that were bascially SQL queries. All my select * is getting replced with filenames! This use of variables is ridiculous. – Thyag Nov 29 '18 at 21:52
  • Changing `echo $LINE` to `echo "$LINE"` would solve 95% of the problem. (Changing `read LINE` to `IFS= read -r LINE` and replacing the `echo` entirely with `printf '%s\n' "$LINE"` would solve the rest, when given a valid UNIX input file; to handle invalid files without trailing newlines, one also needs the `|| [[ $LINE ]]` in cppcoder's answer or some other alternative thereto). See [BashPitfalls #14](http://mywiki.wooledge.org/BashPitfalls#echo_.24foo), and [BashFAQ #1](http://mywiki.wooledge.org/BashFAQ/001). – Charles Duffy Nov 29 '18 at 22:17
25

Use:

filename=$1
IFS=$'\n'
for next in `cat $filename`; do
    echo "$next read from $filename" 
done
exit 0

If you have set IFS differently you will get odd results.

electronix384128
  • 6,625
  • 11
  • 45
  • 67
user3546841
  • 559
  • 4
  • 2
  • 35
    [This is a horrible method](http://mywiki.wooledge.org/DontReadLinesWithFor). Please don't use it unless you want to have problems with globbing that will take place before you realize it! – gniourf_gniourf May 20 '14 at 12:28
  • 1
    This is not horrible, no break in the execution. – MUY Belgium Mar 17 '15 at 14:56
  • 16
    @MUYBelgium did you try with a file that contains a single `*` on a line? Anyway, ***this is an antipattern***. [Don't read lines with for](http://mywiki.wooledge.org/DontReadLinesWithFor). – gniourf_gniourf Oct 14 '15 at 16:44
  • This is a good method, I recommend it for more complex scripts. See my comment at the `read` answer. – Ondra Žižka Dec 10 '16 at 14:40
  • 3
    @OndraŽižka, the `read` approach is [the best-practices approach by community consensus](http://mywiki.wooledge.org/BashFAQ/001). The caveat you mention in your comment is one that applies when your loop runs commands (such as `ffmpeg`) that read from stdin, trivially solved by using a non-stdin FD for the loop or redirecting such commands' input. By contrast, working around the globbing bug in your `for`-loop approach means making (and then needing to reverse) shell-global settings changes. – Charles Duffy Dec 14 '16 at 16:14
  • 2
    @OndraŽižka, ...moreover, the `for` loop approach you use here means that all content has to be read in before the loop can start executing at all, making it entirely unusable if you're looping over gigabytes of data even if you *have* disabled globbing; the `while read` loop needs to store no more than a single line's data at a time, meaning it can start executing while the subprocess generating content is still running (thus being usable for streaming purposes), and also has bounded memory consumption. – Charles Duffy Dec 14 '16 at 16:18
  • 1
    (To amplify the concurrency differences a bit further: A `while read` loop can start one stage of processing while the prior stage is still generating more data; a `for` loop needs collection to finish before it can do anything. Moreover, there are constant-time performance differences as well: ```for next in `cat filename` ``` involves a `fork()` call to create the subshell, an `execve()` to replace that subshell with `/bin/cat`, and all the linker/loader overhead involved in running an external tool; a `while read` loop does everything necessary internal to the shell itself). – Charles Duffy Dec 14 '16 at 16:37
  • Ok you got me. (Although I don't consider bash as a language for processing gigabyte-sized files, and also, keeping in mind that I need to do a workaround for this or that kind of script isn't really a developer friendly.) I don't understand what do you mean by "redirecting such command's input". – Ondra Žižka Dec 19 '16 at 01:42
  • @OndraŽižka, re: redirecting -- ` – Charles Duffy May 21 '17 at 21:35
  • 1
    Actually, even `while`-based approaches seem to have *-character issues. See comments of accepted answer above. Not arguing against for-iteration over files being an antipattern, though. – Egor Hans Nov 12 '17 at 14:50
22

Many people have posted a solution that's over-optimized. I don't think it is incorrect, but I humbly think that a less optimized solution will be desirable to permit everyone to easily understand how is this working. Here is my proposal:

#!/bin/bash
#
# This program reads lines from a file.
#

end_of_file=0
while [[ $end_of_file == 0 ]]; do
  read -r line
  # the last exit status is the 
  # flag of the end of file
  end_of_file=$?
  echo $line
done < "$1"
electronix384128
  • 6,625
  • 11
  • 45
  • 67
Raul Luna
  • 1,945
  • 1
  • 17
  • 26
12

If you need to process both the input file and user input (or anything else from stdin), then use the following solution:

#!/bin/bash
exec 3<"$1"
while IFS='' read -r -u 3 line || [[ -n "$line" ]]; do
    read -p "> $line (Press Enter to continue)"
done

Based on the accepted answer and on the bash-hackers redirection tutorial.

Here, we open the file descriptor 3 for the file passed as the script argument and tell read to use this descriptor as input (-u 3). Thus, we leave the default input descriptor (0) attached to a terminal or another input source, able to read user input.

Community
  • 1
  • 1
gluk47
  • 1,812
  • 20
  • 31
7

For proper error handling:

#!/bin/bash

set -Ee    
trap "echo error" EXIT    
test -e ${FILENAME} || exit
while read -r line
do
    echo ${line}
done < ${FILENAME}
Billal Begueradj
  • 20,717
  • 43
  • 112
  • 130
bviktor
  • 1,356
  • 13
  • 14
1

Use IFS (internal field separator) tool in bash, defines the character using to separate lines into tokens, by default includes <tab> /<space> /<newLine>

step 1: Load the file data and insert into list:

# declaring array list and index iterator
declare -a array=()
i=0

# reading file in row mode, insert each line into array
while IFS= read -r line; do
    array[i]=$line
    let "i++"
    # reading from file path
done < "<yourFullFilePath>"

step 2: now iterate and print the output:

for line in "${array[@]}"
  do
    echo "$line"
  done

echo specific index in array: Accessing to a variable in array:

echo "${array[0]}"
avivamg
  • 12,197
  • 3
  • 67
  • 61
-9

The following will just print out the content of the file:

cat $Path/FileName.txt

while read line;
do
echo $line     
done
Jared Forth
  • 1,577
  • 6
  • 17
  • 32
  • 6
    This answer really doesn’t add anything over existing answers, doesn’t work due to a typo/bug, and breaks in many ways. – Konrad Rudolph May 21 '19 at 08:59