Eu implementei isso com um documento aqui:
i=1
while read -r line; do
eval VAL$i=\$line
i=$((i+1))
done <<EOF
$(my_command)
EOF
Funciona bem.
Atualização: Comentários incorporados de Gilles e mikeserv.
Em um script de shell, preciso analisar a saída de um comando linha por linha. A saída pode incluir linhas vazias e estas são relevantes. Eu estou usando cinzas, não bash, então não posso recorrer à substituição de processos. Eu estou tentando isso:
OUT='my_command'
IFS=$'\n'
i=1
for line in $OUT; do
echo $line
eval VAL$i=$line
i=$((i+1))
done
No entanto, isso está descartando linhas vazias em $ OUT. Como posso corrigir isso para que as linhas vazias também sejam processadas?
set -f -- "-$-"' -- "$@" '"
${IFS+IFS=\} ${out+out=\}" \
"$IFS" "$out" "$@"
IFS='
';for out in $(my command|grep -n '.\|')
do : something with "${out%%:*}" and "${out#*:}"
done
unset IFS out
eval "set +f $1"
shift 3
Você precisa apenas organizá-lo para que não haja linhas em branco. Embora inicialmente eu tenha sugerido nl
para essa finalidade, pensando bem, há uma pequena chance de que o divisor de páginas lógicas de nl
possa ocorrer na entrada e distorcer sua saída (ela acabaria resultando em uma linha em branco, na verdade, e influenciaria qual linha foi numerada - é um recurso muito útil para outras finalidades) . Além de não interpretar quebras de página lógicas, os resultados de grep -n '.\|'
serão idênticos.
Usando um pipeline como esse com uma pequena substituição de parâmetro e você pode não apenas evitar o problema da linha em branco, mas também cada iteração vem pré-numerada ao mesmo tempo - (o número atual da iteração será no momento cabeça de cada valor serviu para você por $out
seguido por um :
) .
As linhas set ... IFS=...
estão lá para garantir que o estado do shell seja restaurado para onde você o deixou antes de alterá-lo. Essas precauções podem ser exageradas se for um script e não uma função. Ainda assim, você deve pelo menos set -f
antes da divisão do shell para evitar globalização não intencional em sua entrada.
(d)ash
e <(
substituição de processo )
Então, novamente, em um Debian ( dash
) derivado ash
(como busybox ash
) você pode achar que o seu tratamento de links de descritor de arquivo e aqui-documents fornece uma alternativa superior ao que você pode estar acostumado a fazer com <(
process substitution )
.
Considere este exemplo:
exec "$((i=3))"<<R "$((o=4))"<<W 3<>/dev/fd/3 4<>/dev/fd/4
R
W
sed -u 's/.*/here I am./' <&"$o" >&"$i" &
echo "hey...sed?" >&"$o"
head -n1 <&"$i"
Porque dash
e derivados de volta aqui - documentos com caches anônimas em vez de (como a maioria das outras shells) com arquivos regulares, e porque os links /dev/fd/[num]
nos sistemas linux fornecem uma maneira indireta de referir-se ao arquivo de apoio do descritor de arquivo (mesmo quando não pode ser referenciado em um sistema de arquivos - como para pipes anônimos) a seqüência acima demonstra um meio muito simples de configurar o que alguns shells podem consulte como coprocess . Por exemplo, em busybox ash
ou dash
em um sistema linux (não atestarei outros) o acima será impresso:
here I am.
... e continuará fazendo isso até que o shell feche seus descritores de arquivo $i
e $o
. Aproveita a opção -u
nbuffered do GNU sed
para evitar problemas de buffer, mas mesmo sem ela a entrada do processo em segundo plano pode ser filtrada e conv=sync
hronized em blocos de
bytes w / dd
NULsed
em um pipeline, se necessário.
Aqui está uma maneira em que normalmente uso o acima com sed
em um shell interativo:
: & SEDD=$$$!
sed -un "/^$SEDD$/!H;//!d;s///;x;/\n/!q;s///;s/%/&&/g;l" <&"$o" >&"$i" &
... quais fundos a %
que lerá e armazenará entrada até encontrar um delimitador exclusivo, quando dobrará qualquer ocorrência de H
em seu buffer exec
old e imprimirá em meu sed
'd anonymous cana uma string amigável com C no formato printf em uma única linha - ou, em várias linhas, se o resultado for maior que 80 caracteres. Este último - para o GNU sed -l0
- pode ser tratado com sed
, que é um parâmetro que instruiria \
a não quebrar linhas em $fmt
, ou então:
fmt=
while IFS= read -r r <&"$i"
case $r in (*$)
! fmt=$fmt$r ;;esac
do fmt=$fmt${r%?}
done
De qualquer forma, eu construo seu buffer como:
echo something at sed >&"$o"
printf '%s\n' more '\lines%' at sed "$SEDD" >&"$o"
Então eu puxo-o como ...
IFS= read -r fmt <&"$i"
Este é o conteúdo de sed
depois:
printf %s\n "$fmt"
something at sed\nmore\n\lines%%\nat\nsed$
sed
também fará escutas octal no estilo C para caracteres não imprimíveis.
Então eu posso usá-lo como ...
printf "%d\n${fmt%$}\n" 1 2 3
... que imprime ...
1
something at sed
more
\lines%
at
sed
2
something at sed
more
\lines%
at
sed
3
something at sed
more
\lines%
at
sed
E eu posso matar %code% e liberar os canos conforme necessário ...
printf %s\n "$SEDD" "$SEDD" >&"$o"
exec "$i">&- "$o">&-
Esse é o tipo de coisa que você pode fazer quando consegue segurar um fd em vez de usá-lo apenas uma vez. Você pode manter um back-pipe pelo tempo que for necessário - e é mais seguro do que um pipe nomeado porque o kernel não oferece esses links para nenhum, exceto o processo que os possui ( seu shell) , enquanto um pipe nomeado pode ser encontrado (e tocado / roubado) em um sistema de arquivos por qualquer processo com permissões para seu arquivo de referência.
Para fazer coisas semelhantes em um shell que processa a substituição, você provavelmente pode gostar ...
eval "exec [num]<>"<(:)
... mas eu nunca tentei isso.
Faça assim:
i=1
my_command | while read line; do
echo $line
eval VAL$i="$line"
i=$((i+1))
done
Como a saída do comando é lida linha por linha, essas linhas são processadas individualmente (incluindo linhas vazias) sem ter que armazenar essas linhas em uma variável primeiro. Isso também economiza memória, já que a saída não acaba na memória duas vezes, e o script bash pode começar a processar essas linhas assim que elas saírem e não apenas após o comando ter sido concluído.
EDIT: Como as variáveis VALx são definidas em uma subshell acima, é necessária uma modificação:
eval 'i=1
my_command | while read line; do
# echo $line
echo "VAL$i=\"$line\""
i=$((i+1))
done'
Se você realmente precisar do echo $line
, algumas modificações serão necessárias.
Tags shell scripting shell-script