Como coletar corretamente uma matriz de linhas no zsh


42

Eu pensei que o seguinte agruparia a saída de my_commandem uma matriz de linhas:

IFS='\n' array_of_lines=$(my_command);

de modo que $array_of_lines[1]se referisse à primeira linha na saída de my_command, $array_of_lines[2]à segunda e assim por diante.

No entanto, o comando acima parece não funcionar bem. Parece também dividir a saída em my_commandtorno do caractere n, como verifiquei com print -l $array_of_lines, que acredito que imprime elementos de uma matriz linha por linha. Também verifiquei isso com:

echo $array_of_lines[1]
echo $array_of_lines[2]
...

Numa segunda tentativa, pensei que adicionar evalpoderia ajudar:

IFS='\n' array_of_lines=$(eval my_command);

mas obtive exatamente o mesmo resultado que sem ele.

Por fim, seguindo a resposta em Listar elementos com espaços no zsh , também tentei usar sinalizadores de expansão de parâmetros em vez de IFSdizer ao zsh como dividir a entrada e coletar os elementos em uma matriz, ou seja:

array_of_lines=("${(@f)$(my_command)}");

Mas eu ainda tenho o mesmo resultado (divisão acontecendo n)

Com isso, tenho as seguintes perguntas:

Q1 Quais são as formas "apropriadas" de coletar a saída de um comando em uma matriz de linhas?

Q2 Como posso especificar IFSa divisão apenas em novas linhas?

Q3 Se eu usar sinalizadores de expansão de parâmetros como na minha terceira tentativa acima (ou seja, usar @f) para especificar a divisão, o zsh ignora o valor de IFS? Por que não funcionou acima?

Respostas:


71

TL, DR:

array_of_lines=("${(@f)$(my_command)}")

Primeiro erro (→ Q2): IFS='\n'define IFSpara os dois caracteres \e n. Para definir IFSuma nova linha, use IFS=$'\n'.

Segundo erro: para definir uma variável para um valor de matriz, você precisa de parênteses em torno dos elementos: array_of_lines=(foo bar).

Isso funcionaria, exceto que remove linhas vazias, porque espaços em branco consecutivos contam como um único separador:

IFS=$'\n' array_of_lines=($(my_command))

Você pode reter as linhas vazias, exceto no final, dobrando o caractere de espaço em branco em IFS:

IFS=$'\n\n' array_of_lines=($(my_command))

Para continuar rastreando linhas vazias também, você teria que adicionar algo à saída do comando, porque isso acontece na própria substituição do comando, e não na análise.

IFS=$'\n\n' array_of_lines=($(my_command; echo .)); unset 'array_of_lines[-1]'

(supondo que a saída de my_commandnão termine em uma linha não delimitada; observe também que você perde o status de saída de my_command)

Observe que todos os snippets acima saem IFScom seu valor não padrão, portanto, eles podem atrapalhar o código subseqüente. Para manter a configuração de IFSlocal, coloque tudo em uma função na qual você declara IFSlocal (aqui também cuidando da preservação do status de saída do comando):

collect_lines() {
  local IFS=$'\n\n' ret
  array_of_lines=($("$@"; ret=$?; echo .; exit $ret))
  ret=$?
  unset 'array_of_lines[-1]'
  return $ret
}
collect_lines my_command

Mas eu recomendo não mexer com IFS; em vez disso, use o fsinalizador de expansão para dividir em novas linhas (→ Q1):

array_of_lines=("${(@f)$(my_command)}")

Ou para preservar as linhas vazias à direita:

array_of_lines=("${(@f)$(my_command; echo .)}")
unset 'array_of_lines[-1]'

O valor de IFSnão importa lá. Eu suspeito que você tenha usado um comando que se divide IFSpara imprimir $array_of_linesem seus testes (→ Q3).


7
Isso é tão complicado! "${(@f)...}"é o mesmo que ${(f)"..."}, mas de uma maneira diferente. (@)aspas duplas internas significa "produzir uma palavra por elemento da matriz" e (f)"dividir em uma matriz por nova linha". PS: Link para os documentos
flying sheep o

3
@flyingsheep, não ${(f)"..."}iria pular linhas vazias, "${(@f)...}"preserva-os. Essa é a mesma distinção entre $argve "$argv[@]". Essa "$@"coisa para preservar todos os elementos de uma matriz vem do shell Bourne no final dos anos 70.
Stéphane Chazelas

4

Duas questões: primeiro, aspas aparentemente duplas também não interpretam escapes de barra invertida (desculpe por isso :). Use $'...'aspas. E de acordo com man zshparam, para coletar palavras em uma matriz, você precisa colocá-las entre parênteses. Então, isso funciona:

% touch 'a b' c d 'e f'
% IFS=$'\n' arr=($(ls)); print -l $arr
a b
c
d
e f
% print $arr[1]
a b

Não consigo responder ao seu terceiro trimestre. Espero nunca ter que saber coisas tão esotéricas :).


-2

Você também pode usar tr para substituir a linha nova por espaço:

lines=($(mycommand | tr '\n' ' '))
select line in ("${lines[@]}"); do
  echo "$line"
  break
done

3
e se as linhas contiverem espaços?
21916 Don_crissti

2
Isso não faz sentido. O SPC e o NL estão no valor padrão de $IFS. Traduzir um para o outro não faz diferença.
Stéphane Chazelas

Minhas edições foram razoáveis? Eu não consegui fazê-lo funcionar como era,
John P

(excedeu o tempo limite) Admito que o editei sem realmente entender qual era a intenção, mas acho que a tradução é um bom começo para a separação com base na manipulação de strings. Eu não traduziria para espaços e os dividiria, a menos que o comportamento esperado fosse mais parecido echo, onde entradas são todas mais ou menos pilhas de palavras separadas por quem se importa.
John P
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.