0

I'm using sh (not bash/csh) on FreeBSD 11, and I don't understand this:

In console

Command: zpool status -v

Result:

  pool: My_pool
 state: ONLINE
status: One or more devices is currently being resilvered.  The pool will
        continue to function, possibly in a degraded state.
action: Wait for the resilver to complete.

Splitting by lines and printing line at a time in a script:

#!/bin/sh
zp="$(zpool status -v)"
for line in $zp; do
  echo "$line%"
done

Result:

pool:%
My_pool%
state:%
ONLINE%
status:%
One%
or%
more%
devices%
is%
currently%
being%
resilvered.%
The%
pool%
will%
continue%
to%
function,%
possibly%
in%
a%
degraded%
state.%
action:%
Wait%

According to all I can find, the syntax I'm using is correct for sh, and should read a line at a time, not a word at a time.

What am I missing?

0

2 Answers 2

3

I'm puzzled about what you found.The syntax you used is correct for sh and splits at whitespace, not just newlines. This is widely documented. Thinking that it splits at line breaks is not a popular misconception. (Not understanding that it does split is a popular misconception.)

More precisely, leaving a variable expansion unquoted, as in $zp, is sometimes nicknamed the “split+glob” operator. What it does is:

  1. Take the value of the variable. That's a string.
  2. Split this value at every field separator character. A field separator character is one that's in the value of the variable IFS; by default, this variable contains a space, a tab and a newline. (This default is also used if the variable is unset.) The result is a list of strings.
  3. Consider each element of the list as a wildcard (glob) pattern. If it matches one or more file names, that element is replaced by the list of matching file names. Otherwise the element is left alone.

For example, if the directory contains files called foo, bar and baz, then the following script prints three lines: a*, bar and baz.

zq='a* b*'
for word in $zq; do echo "$word"; done

If you want to split a string at newlines, then you can do it by setting IFS to a newline and disabling wildcard expansion.

#!/bin/sh
set -f
IFS='
'
zq=…
for line in $zq; do
  …
done

This applies to all sh and csh variants.

See also Why does my shell script choke on whitespace or other special characters?

0

To read lines, use your shell's built-in read function,

$ help read | head -2

read: read [-ers] [-u fd] [-t timeout] [-p prompt] [-a array] [-n nchars] [-d delim] [name ...]

One line is read from the standard input, or from file descriptor FD if the -u option is supplied

Put it inside a while loop and, voila, you have lines:

$ wc -l -w ~/.profile 
  24      47 /Users/jklowden/.profile
$ for W in $(cat ~/.profile); do echo $W; done | wc -l 
  47
$ while read L; do echo $L; done < ~/.profile | wc -l 
  24
1
  • Even the first line of data is mangled by this plain read loop. Consider while IFS= read -r L and echo "$L". Quote your variables! Commented Nov 29, 2017 at 8:07

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .