58

Following is the shell script to read all the DSF present in the box. But since the line is having spaces, it is displaying them in different lines. For those of you who dont understand ioscan -m dsf, replace it by ls -ltr, then the output is such that the permission and names are displayed in different line, but i want them in the same line.

#!/usr/bin/ksh

for a in `ioscan -m dsf`
do
 echo  $a
done
Lesmana
  • 25,663
  • 9
  • 82
  • 87
randeepsp
  • 3,572
  • 9
  • 33
  • 40

3 Answers3

83

The for loop is not designed to loop over lines (things separated by newline). Instead it loops over words (things separated by space).

The idiomatic way to loop over lines is to use a while loop in combination with read.

ioscan -m dsf | while read -r line
do
  printf '%s\n' "$line"
done

Note that the while loop is in a subshell because of the pipe. This can cause some confusion with variable scope. In bash you can work around this by using process substitution.

while read -r line
do
  printf '%s\n' "$line"
done < <(ioscan -m dsf)

But now the "generator" (ioscan in this example) is in a subshell.

For more information about the subshell problematic in loops see http://mywiki.wooledge.org/BashFAQ/024


technical nitpick: words are things separated not only by space. they are typically also separated by tab and newlines. the separator separating the words is defined in the IFS variable. IFS is short for Internal Field Separator. in bash lingo words are called fields. Usually $IFS contains a space, a tab, and a newline.

Often you will see the suggestion to change the value of $IFS to only newline.

Here is the typical way to do so:

OLDIFS="$IFS"
IFS=$'\n' # bash specific
for line in $(ioscan -m dsf)
do
  printf '%s\n' "$line"
done
IFS="$OLDIFS"

(the bash specific part ($'\n') is called ANSI-C Quoting)

But beware many commands depends on some sane setting for $IFS. I do not recommend changing $IFS. Too often it will cause an endless nightmare of obscure bug hunting.


See also:

Lesmana
  • 25,663
  • 9
  • 82
  • 87
17

Using for

for l in $() performs word splitting based on IFS:

$ for l in $(printf %b 'a b\nc'); do echo "$l"; done
a
b
c
$ IFS=$'\n'; for l in $(printf %b 'a b\nc'); do echo "$l"; done
a b
c

IFS doesn't have to be set back if it is not used later.

for l in $() also performs pathname expansion:

$ printf %b 'a\n*\n' > file.txt
$ IFS=$'\n'
$ for l in $(<file.txt); do echo "$l"; done
a
file.txt
$ set -f; for l in $(<file.txt); do echo "$l"; done; set +f
a
*

If IFS=$'\n', linefeeds are stripped and collapsed:

$ printf %b '\n\na\n\nb\n\n' > file.txt
$ IFS=$'\n'; for l in $(<file.txt); do echo "$l"; done
a
b

$(cat file.txt) (or $(<file.txt)) also reads the whole file to memory.

Using read

Without -r backslashes are used for line continuation and removed before other characters:

$ cat file.txt
\1\\2\
3
$ cat file.txt | while read l; do echo "$l"; done
1\23
$ cat file.txt | while read -r l; do echo "$l"; done
\1\\2\
3

Characters in IFS are stripped from the start and end of lines but not collapsed:

$ printf %b '1  2 \n\t3\n' | while read -r l; do echo "$l"; done
1  2
3
$ printf %b ' 1  2 \n\t3\n' | while IFS= read -r l; do echo "$l"; done
 1  2 
    3

If the last line doesn't end with a newline, read assigns l to it but exits before the body of the loop:

$ printf 'x\ny' | while read l; do echo $l; done
x
$ printf 'x\ny' | while read l || [[ $l ]]; do echo $l; done
x
y

If a while loop is in a pipeline, it is also in a subshell, so variables are not visible outside it:

$ x=0; seq 3 | while read l; do let x+=l; done; echo $x
0
$ x=0; while read l; do let x+=l; done < <(seq 3); echo $x
6
$ x=0; x=8 | x=9; echo $x
0
Lri
  • 26,768
  • 8
  • 84
  • 82
0

you need to use this basically IFS=$'\n' and grep -x instead of grep as it will work like a equal to operator instead of like operator.

psaisara
  • 62
  • 3