Reputation: 3842
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
Upvotes: 65
Views: 61594
Reputation: 27053
The for
loop is not designed to loop over lines. Instead it loops over words. Words are things separated by space. Lines are things separated by newline. More on that later.
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
Alternatively:
while read -r line
do
printf '%s\n' "$line"
done < <(ioscan -m dsf)
Both work fine for most simple cases. The second variant is using a process substitution which might not be available in all shells.
Both variants have advantages and disadvantages which mainly become apparent if you want to manipulate variables inside the loop.
For more information see http://mywiki.wooledge.org/BashFAQ/024
technical nitpick:
words, or fields as they are called in bash, are things separated by space but also by tab and newlines. basically things separated by whitespace.
the separator separating the fields is defined in the IFS
variable (short for Internal Field Separator). Usually $IFS
contains a space, a tab, and a newline.
Often you will see the suggestion to loop over lines by changing the value of $IFS
to only newline.
# not recommended
OLDIFS="$IFS"
IFS=$'\n'
for line in $(ioscan -m dsf)
do
printf '%s\n' "$line"
done
IFS="$OLDIFS"
(the $'\n'
is is called ANSI-C Quoting and might not be available in all shells)
I do not recommend changing $IFS
. Many commands rely on sane setting for $IFS
. Changing $IFS
will often cause an endless nightmare of obscure bug hunting.
See also:
Upvotes: 92
Reputation: 80
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.
Upvotes: 0
Reputation: 27613
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.
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
Upvotes: 21