Ler a saída do comando em uma variável no shell divide o texto em cada espaço em branco, não apenas em nova linha

Ler a saída do comando em uma variável no shell divide o texto em cada espaço em branco, não apenas em nova linha

Estou usando sh (não bash/csh) no FreeBSD 11 e não entendo isso:

Na consola

Comando:zpool status -v

Resultado:

  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.

Dividindo por linhas e imprimindo linha por vez em um script:

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

Resultado:

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%

De acordo com tudo o que encontrei, a sintaxe que estou usando é correta para sh e deve ler uma linha por vez, não uma palavra por vez.

o que estou perdendo?

Responder1

Estou intrigado com o que você encontrou. A sintaxe que você usou está correta para sh e divisões em espaços em branco, não apenas em novas linhas. Isto está amplamente documentado. Pensar que ele se divide nas quebras de linha não é um equívoco popular. (Não entender que isso se divide é um equívoco popular.)

Mais precisamente, deixar uma expansão variável sem aspas, como em $zp, às vezes é apelidado de operador “split+glob”. O que isso faz é:

  1. Pegue o valor da variável. Isso é uma corda.
  2. Divida esse valor em cada caractere separador de campo. Um caractere separador de campo é aquele que está no valor da variável IFS; por padrão, esta variável contém umespaço, uma tabulação e uma nova linha. (Esse padrão também é usado se a variável não estiver definida.) O resultado é uma lista de strings.
  3. Considere cada elemento da lista como um padrão curinga (glob). Se corresponder a um ou mais nomes de arquivos, esse elemento será substituído pela lista de nomes de arquivos correspondentes. Caso contrário, o elemento será deixado sozinho.

Por exemplo, se o diretório contiver arquivos chamados foo, bare baz, o script a seguir imprimirá três linhas: a*, bare baz.

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

Se quiser dividir uma string em novas linhas, você pode fazer isso configurando IFSuma nova linha e desativando a expansão de curinga.

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

Isso se aplica a todas as variantes sh e csh.

Veja tambémPor que meu script de shell engasga com espaços em branco ou outros caracteres especiais?

Responder2

Para ler linhas, use o recurso interno do seu shelllerfunção,

$ajuda a ler | cabeça -2

leitura: leitura [-ers] [-u fd] [-t tempo limite] [-p prompt] [-a array] [-n nchars] [-d delim] [nome ...]

Uma linha é lida da entrada padrão ou do descritor de arquivo FD se a opção -u for fornecida

Coloque-o dentro de um whileloop e pronto, você terá linhas:

$ 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

informação relacionada