220

I am trying to run a for loop for file and I want to display whole line. But instead its displaying last word only. I want the complete line.

for j in `cat ./file_wget_med`

do
echo $j

done

result after run:

Found.

Here is my data:

$ cat file_wget_med
2013-09-11 14:27:03 ERROR 404: Not Found.
Radu Rădeanu
  • 174,089
  • 51
  • 332
  • 407
user192118
  • 2,201

7 Answers7

314

for loop splits when it sees any whitespace like space, tab, or newline. So, you should use IFS (Internal Field Separator):

IFS=$'\n'       # make newlines the only separator
for j in $(cat ./file_wget_med)    
do
    echo "$j"
done
# Note: IFS needs to be reset to default!
famzah
  • 205
  • 2
  • 5
Radu Rădeanu
  • 174,089
  • 51
  • 332
  • 407
133

for loops split on any whitespace (space, tab, newline) by default; the easiest way to work on one line at a time is to use a while read loop instead, which splits on newlines:

while read i; do echo "$i"; done < ./file_wget_med

I would expect your command to spit out one word per line (that's what happened when I tested it out with a file of my own). If something else is happening, I'm not sure what could be causing it.

evilsoup
  • 4,625
24
#!/bin/bash
files=`find <subdir> -name '*'`
while read -r fname; do
    echo $fname
done <<< "$files"

Verified working, not the one liner you probably want but it's not possible to do so elegantly.

8

Here is a slight extension to Mitchell Currie's answer, that I like because of the small scope of side effects, which avoids having to set a variable:

#!/bin/bash
while read -r fname; do
    echo $fname
done <<< "`find <subdir>`"
Dandalf
  • 181
6

I would write it like this:

cat ./file_wget_med | while read -r j
do
    echo $j
done

as it requires least changes in the original script (except for the solution using IFS, but it modifies bash behavior not only for this loop control statement).

mik
  • 161
3

Mapfile is a convenient way to read lines from a file into an indexed array, not as portable as read but slightly faster. By using for loop you avoid creating a subshell.

#!/bin/bash

mapfile -t < file.txt

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

Keep in mind when using pipelines, it will put the while loop in a subshell. Changes inside the while loop like variables will not propagate to outer part of the script.

Example:

#!/bin/bash

a=0 printf %s\n {0..5} | while read; do ((a++)) done echo $a # 'a' will always be 0.

(Better solution):

#!/bin/bash

b=0 while read; do ((b++)) done < <(printf %s\n {0..5})

echo $b # 'b' equal to 6 (works as expected).

Further reading:

-1

Dandalf got real close to a functional solution, but one should NOT EVER be trying to assign the result of unknown amounts of input (i.e. find ~/.gdfuse -name '*') to variables! Or, at least be trying to do such a thing via an array variable; if you insist on being so lazy! So, here's Dandalf's solution done without the dangerous maneuver; and in all in one line

while read -r fname; do
  echo $fname;
done <<< `find ~/.gdfuse -name '*'
Pablo Bianchi
  • 17,371