84

I would like to read a file into a script, line by line. Each line in the file is multiple values separated by a tab, I'd like to read each line into an array.

Typical bash "read file by line" example;

while read line
do
echo $line;
done < "myfile"

For me though, myfile looks like this (tab separated values);

value1 value2 value3
value4 value5 value6

On each iteration of the loop, I'd like each line to go into an array so I can

while read line into myArray
do
 echo myArray[0]
 echo myArray[1]
 echo myArray[2]
done < "myfile"

This would print the following on the first loop iteration;

value1
value2
value3

Then on the second iteration it would print

value4
value5
value6

Is this possible? The only way I can see is to write a small function to break out the values manually, is there built in support in bash for this?

codeforester
  • 39,467
  • 16
  • 112
  • 140
jwbensley
  • 10,534
  • 19
  • 75
  • 93

3 Answers3

193

You're very close:

while IFS=$'\t' read -r -a myArray
do
 echo "${myArray[0]}"
 echo "${myArray[1]}"
 echo "${myArray[2]}"
done < myfile

(The -r tells read that \ isn't special in the input data; the -a myArray tells it to split the input-line into words and store the results in myArray; and the IFS=$'\t' tells it to use only tabs to split words, instead of the regular Bash default of also allowing spaces to split words as well. Note that this approach will treat one or more tabs as the delimiter, so if any field is blank, later fields will be "shifted" into earlier positions in the array. Is that O.K.?)

gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
ruakh
  • 175,680
  • 26
  • 273
  • 307
  • 7
    This is a great answer, thanks for breaking it down like that, I really appreciated it. Just what I needed, thanks :D – jwbensley Mar 16 '12 at 12:19
  • No, if you remove `echo "${myArray[1]}"` and `echo "${myArray[2]}"` it will still give the same output – Ahmed Hussein Dec 17 '19 at 17:35
  • @AhmedHussein: That's an interesting claim. It's obviously false in the OP's case -- `echo` adds a newline, and the OP could hardly be confused about whether the output had newlines in the right places -- but if you have a similar-but-different situation, I invite you to post your own question, with enough detail to let someone help you. (And if you comment here with a link to your question, I'll take a look.) – ruakh Dec 17 '19 at 18:28
  • I am trying this on the geonames dumps, and sometimes there are multiple columns empty, meaning multiple tabs are treated as one seeing the "shift" as you describe. How can I avoid that? – giorgio79 Jan 17 '21 at 08:39
  • 1
    @giorgio79: The cleanest way I can think of is to read the entire line into a variable (e.g. `IFS= read -r line`), then use `readarray` to essentially "split" that variable into an array: `readarray -d $'\t' -t myArray < <(printf %s "$line")`. (Note: the reason for `< <(printf %s "$line")` instead of `<<<"$line"` is that the latter would tack on a newline, which would then get included in the last field.) – ruakh Jan 17 '21 at 09:33
  • @ruakh: Perfect, clear, and explained answer. Good job! – schweik Aug 04 '23 at 12:30
21

If you really want to put the values in an array, then @ruakh's answer is the correct approach. But read also supports putting each value in a separate variable, which is more readable if you have meaningful names you can use for them. For example, if the three columns are user-ID, username, and e-mail address, then you might write:

while IFS=$'\t' read -r user_id username email ; do
  echo "${user_id}"
  echo "${username}"
  echo "${email}"
done < "myfile"
ruakh
  • 175,680
  • 26
  • 273
  • 307
slylittl3
  • 231
  • 3
  • 3
  • Actually `-r` added by @gniourf_gniourf avoid the expansion of backslashed characters, if doing so `%b`could be substituted by `%s` in the `printf` format strings cause backslashed scaped characters will be represented as literals. So, use it or not depending on what you really want to do. – slylittl3 Feb 19 '15 at 02:05
0

You could also try,

OIFS=$IFS;
IFS="\t";

animals=`cat animals.txt`
animalArray=$animals;

for animal in $animalArray
do
    echo $animal
done

IFS=$OIFS;