25

I've read this question about how to read n characters from a text file using bash. I would like to know how to read a word at a time from a file that looks like:

example text
example1 text1
example2 text2
example3 text3

Can anyone explain that to me, or show me an easy example? Thanks!

Community
  • 1
  • 1
maniat1k
  • 440
  • 2
  • 6
  • 14

8 Answers8

27

The read command by default reads whole lines. So the solution is probably to read the whole line and then split it on whitespace with e.g. for:

#!/bin/sh

while read line; do
    for word in $line; do
        echo "word = '$word'"
    done
done <"myfile.txt"
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • Where should I can the name of my file in the above code? – Admia Feb 03 '17 at 21:29
  • 1
    @Admia Remember that `read` reads from standard input. The "best" way (IMO) to solve it is to put the code in a function. And since functions can be called just like any other command, it can also be used with redirection. So then you can do e.g. `function_with_read_loop < some_file`. – Some programmer dude Feb 04 '17 at 07:51
  • 2
    If your line contains `*` as a standalone element, you'll get a list of filenames in the current directory from `for word in $line`. (Also, `read` without `-r` will munge backslash literals). – Charles Duffy Aug 01 '17 at 16:43
24

The way to do this with standard input is by passing the -a flag to read:

read -a words
echo "${words[@]}"

This will read your entire line into an indexed array variable, in this case named words. You can then perform any array operations you like on words with shell parameter expansions.

For file-oriented operations, current versions of Bash also support the mapfile built-in. For example:

mapfile < /etc/passwd
echo ${MAPFILE[0]}

Either way, arrays are the way to go. It's worth your time to familiarize yourself with Bash array syntax to make the most of this feature.

Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
  • This question is tagged bash, but for future readers, note these are both bash-specific and will not work in the posix shell. – John Hascall Jul 17 '18 at 18:25
15

Ordinarily, you should read from a file using a while read -r line loop. To do this and parse the words on the lines requires nesting a for loop inside the while loop.

Here is a technique that works without requiring nested loops:

for word in $(<inputfile)
do
    echo "$word"
done
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • 1
    I wouldn't say that string-splitting is "ordinarily" correct. If you have a `*` in your file, you'll get a list of names in the current directory in your output. – Charles Duffy Aug 01 '17 at 16:27
  • @CharlesDuffy: That issue also affects the accepted answer (the technique described in my second sentence). The technique in my code snippet fails in the same way. – Dennis Williamson Aug 01 '17 at 16:38
10

In the context given, where the number of words is known:

while read -r word1 word2 _; do
  echo "Read a line with word1 of $word1 and word2 of $word2"
done

If you want to read each line into an array, read -a will put the first word into element 0 of your array, the second into element 1, etc:

while read -r -a words; do
  echo "First word is ${words[0]}; second word is ${words[1]}"
  declare -p words # print the whole array
done
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
3

In bash, just use space as delimiter (read -d ' '). This method requires some preprocessing to translate newlines into spaces (using tr) and to merge several spaces into a single one (using sed):

{
 tr '\n' ' ' | sed 's/  */ /g' | while read -d ' ' WORD
 do
  echo -n "<${WORD}> "
 done
 echo
} << EOF
Here you have some words, including * wildcards
that don't get expanded,
multiple   spaces   between   words,
    and lines with spaces at the begining.
EOF

The main advantage of this method is that you don't need to worry about the array syntax and just work as with a for loop, but without wildcard expansion.

eocanha
  • 496
  • 4
  • 5
1

I came across this question and the proposed answers, but I don't see listed this simple possibile solution:

for word in `cat inputfile`
do
  echo $word
done
  • 2
    This isn't listed for a good reason: It's extremely buggy. If your input file has a `*` in it, you'll get a list of files in the current directory in your output -- and because the argument to `echo` isn't quoted, if one of your filenames splits into a set of words that contains a glob, that glob will *also* be expanded. See also [Why you don't read lines with `for`](http://mywiki.wooledge.org/DontReadLinesWithFor). – Charles Duffy Mar 06 '18 at 21:18
1

This can be done using AWK too:

awk '{for(i=1;i<=NF;i++) {print $i}}' text_file
Daniel Böhmer
  • 14,463
  • 5
  • 36
  • 46
Mr Kashyap
  • 562
  • 1
  • 5
  • 16
1

You can combine xargs which reads word delimited by space or newline and echo to print one per line:

<some-file xargs -n1 echo

some-command | xargs -n1 echo

That also works well for large or slow streams of data because it does not need to read the whole input at once.

I’ve used this to read 1 table name at a time from SQLite which prints table names in a column layout:

sqlite3 db.sqlite .tables | xargs -n1 echo | while read table; do echo "1 table: $table"; done
Daniel Böhmer
  • 14,463
  • 5
  • 36
  • 46