Desejo capturar a saída exata de uma substituição de comando, incluindo os caracteres de nova linha à direita .
Sei que eles são removidos por padrão, portanto, pode ser necessária alguma manipulação para mantê-los e desejo manter o código de saída original .
Por exemplo, dado um comando com um número variável de novas linhas à direita e código de saída:
f(){ for i in $(seq "$((RANDOM % 3))"); do echo; done; return $((RANDOM % 256));}
export -f f
Eu quero executar algo como:
exact_output f
E faça com que a saída seja:
Output: $'\n\n'
Exit: 5
Estou interessado em ambos bash
e no POSIX sh
.
$IFS
, portanto, não será capturada como argumento.IFS
(tente( IFS=:; subst=$(printf 'x\n\n\n'); printf '%s' "$subst" )
. Somente novas linhas são removidas.\t
E ``IFS
não afetam e não afetam isso.tcsh
Respostas:
Cartuchos POSIX
O truque usual ( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ) para obter o stdout completo de um comando é:
A idéia é adicionar e extra
.\n
. A substituição de comandos apenas tira isso\n
. E você tira o.
com${output%.}
.Observe que em shells que não sejam
zsh
, isso ainda não funcionará se a saída tiver bytes NUL. Comyash
, isso não funcionará se a saída não for texto.Observe também que, em alguns locais, importa qual caractere você usa para inserir no final.
.
geralmente deve estar bem, mas outros podem não estar. Por exemplox
(conforme usado em outras respostas) ou@
não funcionaria em um código de idioma usando os conjuntos de caracteres BIG5, GB18030 ou BIG5HKSCS. Nesses charsets, a codificação de um número de caracteres termina no mesmo byte que a codificação dex
ou@
(0x78, 0x40)Por exemplo,
ū
em BIG5HKSCS é 0x88 0x78 (ex
é 0x78 como em ASCII, todos os conjuntos de caracteres em um sistema devem ter a mesma codificação para todos os caracteres do conjunto de caracteres portátil que inclui letras em inglês@
e.
). Então, secmd
foiprintf '\x88'
e inserimosx
depois disso,${output%x}
falharia em remover issox
como$output
realmente conteriaū
.Em
.
vez disso, o uso poderia levar ao mesmo problema na teoria se houvesse caracteres cuja codificação terminasse na mesma codificação que.
, mas por ter verificado há algum tempo, posso dizer que nenhum dos charsets disponíveis para uso em um código de idioma em os sistemas Debian, FreeBSD ou Solaris têm esses caracteres que são bons o suficiente para mim (e por que eu decidi.
qual também é o símbolo para marcar o final de uma frase em inglês, parece apropriado).Uma abordagem mais correta, conforme discutida pelo @Arrow, seria alterar o código de idioma para C apenas para a remoção do último caractere (
${output%.}
), que garantiria a remoção de apenas um byte, mas isso complicaria significativamente o código e potencialmente introduziria problemas de compatibilidade de próprio.alternativas bash / zsh
Com
bash
ezsh
, supondo que a saída não tenha NULs, você também pode:Para obter o status de saída
cmd
, você pode fazerwait "$!"; ret=$?
embash
, mas não emzsh
.rc / es / akanaga
Para completar, nota que
rc
/es
/akanga
tem um operador para isso. Neles, a substituição de comando, expressa como`cmd
(ou`{cmd}
para comandos mais complexos), retorna uma lista (dividindo em$ifs
, space-tab-newline por padrão). Nessas cascas (ao contrário das cascas tipo Bourne), a remoção da nova linha é feita apenas como parte dessa$ifs
divisão. Portanto, você pode esvaziar$ifs
ou usar o``(seps){cmd}
formulário em que você especifica os separadores:ou:
De qualquer forma, o status de saída do comando é perdido. Você precisaria incorporá-lo na saída e extraí-lo depois, o que se tornaria feio.
peixe
Nos peixes, a substituição de comandos é com
(cmd)
e não envolve um subshell.Cria uma
$var
matriz com todas as linhas na saída decmd
if$IFS
se não estiver vazia ou com a saída decmd
stripped de até um caractere de nova linha (em oposição a todos na maioria dos outros shells) se$IFS
estiver vazio.Portanto, ainda há um problema nisso
(printf 'a\nb')
e(printf 'a\nb\n')
expanda para a mesma coisa, mesmo com um vazio$IFS
.Para contornar isso, o melhor que pude sugerir foi:
Uma alternativa é fazer:
Casca de Bourne
O shell Bourne não suportava o
$(...)
formulário nem o${var%pattern}
operador, portanto, pode ser bastante difícil de obter lá. Uma abordagem é usar eval e citar:Aqui, estamos gerando uma
para ser passado para
eval
. Quanto à abordagem POSIX, se'
fosse um desses caracteres cuja codificação pode ser encontrada no final de outros caracteres, teríamos um problema (muito pior, pois se tornaria uma vulnerabilidade de injeção de comando), mas felizmente, como.
, não é um deles, e essa técnica de citação geralmente é a que é usada por qualquer coisa que cite o código do shell (observe que\
há um problema, portanto não deve ser usado (também exclui o"..."
interior do qual você precisa usar barras invertidas para alguns caracteres)) Aqui, só o estamos usando depois de um valor'
que está correto).tcsh
Veja tcsh preserve newlines na substituição de comando `...`
(não cuidando do status de saída, que você pode resolver salvando-o em um arquivo temporário (
echo $status > $tempfile:q
após o comando))fonte
zsh
pode armazenarNUL
em uma variável, por que nãoIFS= read -rd '' output < <(cmd)
funcionaria? Ele precisa ser capaz de armazenar o comprimento de uma string ... ela codifica''
como uma string de 1 byte em\0
vez de uma string de 0 byte?read -d ''
é tratado comoread -d $'\0'
(bash
assim$'\0'
como em''
todos os lugares).x
se foi isso que foi adicionado. Por favor, dê uma olhada na minha resposta editada.var=value command eval
truque foi discutido aqui ( também ) e na lista de discussão do grupo austin antes. Você verá que não é portátil (e é bastante óbvio, quando você está tentando coisas comoa=1 command eval 'unset a; a=2'
ou pior, que não foi feito para ser usado assim). O mesmo para osavedVAR=$VAR;...;VAR=$savedVAR
que não faz o que você deseja quando$VAR
estava inicialmente desconfigurado. Se isso é para solucionar apenas um problema teórico (um bug que não pode ser atingido na prática), IMO, não vale a pena. Ainda assim, eu vou apoiá-lo por tentar.LANG=C
para remover um byte de uma string? Você está levantando preocupações em torno do ponto real, tudo é fácil de resolver. (1) não foi usado nenhum ajuste (2) Teste a variável antes de alterá-la. @ StéphaneChazelasPara a nova pergunta, este script funciona:
Na execução:
A descrição mais longa
A sabedoria usual dos shells POSIX para lidar com a remoção de
\n
é:Isso é necessário porque a última nova linha ( S ) é removida pela expansão do comando de acordo com a especificação POSIX :
Sobre uma trilha
x
.Já foi dito nesta pergunta que um
x
poderia ser confundido com o byte à direita de algum caractere em alguma codificação. Mas como vamos adivinhar qual ou qual personagem é melhor em alguma linguagem em alguma codificação possível, que é uma proposição difícil, para dizer o mínimo.Contudo; Isso é simplesmente incorreto .
A única regra que precisamos seguir é adicionar exatamente o que removemos.
Deve ser fácil entender que, se adicionarmos algo a uma string existente (ou sequência de bytes) e depois removermos exatamente o mesmo, a string original (ou sequência de bytes) deverá ser a mesma.
Onde erramos? Quando misturamos caracteres e bytes .
Se adicionarmos um byte, devemos remover um byte; se adicionarmos um caractere, removeremos exatamente o mesmo caractere .
A segunda opção, adicionar um caractere (e depois remover exatamente o mesmo caractere) pode se tornar complicada e complexa e, sim, páginas de código e codificações podem atrapalhar.
No entanto, a primeira opção é bem possível e, depois de explicada, se tornará simples.
Vamos adicionar um byte, um byte ASCII (<127), e para manter as coisas o menos complicado possível, digamos um caractere ASCII no intervalo de az. Ou, como deveríamos dizer, um byte no intervalo hexadecimal
0x61
-0x7a
. Vamos escolher um desses, talvez um x (realmente um byte de valor0x78
). Podemos adicionar esse byte concatenando um x a uma string (vamos assumir umé
):Se olharmos para a string como uma sequência de bytes, veremos:
Uma sequência de cadeias que termina em um x.
Se removermos esse x (valor de byte
0x78
), obtemos:Funciona sem problemas.
Um exemplo um pouco mais difícil.
Digamos que a string em que estamos interessados termine em byte
0xc3
:E vamos adicionar um byte de valor
0xa9
A string tornou-se agora:
Exatamente o que eu queria, os últimos dois bytes são um caractere no utf8 (para que qualquer um possa reproduzir esses resultados em seu console utf8).
Se removermos um caractere, a string original será alterada. Mas não foi isso que adicionamos, adicionamos um valor de byte, que passa a ser escrito como x, mas de qualquer maneira.
O que precisamos para evitar interpretar mal bytes como caracteres. O que precisamos é de uma ação que remova o byte que usamos
0xa9
. De fato, ash, bash, lksh e mksh parecem fazer exatamente isso:Mas não ksh ou zsh.
No entanto, isso é muito fácil de resolver, vamos dizer a todos os shells para remover o byte:
é isso, todos os shells testados funcionam (exceto yash) (para a última parte da string):
Simples assim, diga ao shell para remover um caractere LC_ALL = C, que é exatamente um byte para todos os valores de by
0x00
a0xff
.Solução para comentários:
Para o exemplo discutido nos comentários, uma solução possível (que falha no zsh) é:
Isso removerá o problema da codificação.
fonte
zsh
adicionadoprintf -v
para compatibilidade combash
em dezembro de 2015${var%?}
sempre retire um byte é mais correto em teoria, mas: 1 -LC_ALL
eLC_CTYPE
substitua$LANG
, portanto, você precisará definirLC_ALL=C
2 - você não pode fazer issovar=${var%?}
em um subshell, como seria a alteração ser perdido, portanto, você precisará salvar e restaurar o valor e o estadoLC_ALL
(ou recorrer alocal
recursos de escopo que não sejam do POSIX ). 3- A alteração do código do idioma no meio do script não é totalmente suportada em alguns shells como o yash. Por outro lado, na prática.
nunca é um problema em conjuntos de caracteres da vida real, portanto, usá-lo evita a combinação com LC_ALL.Você pode produzir um caractere após a saída normal e depois removê-lo:
Esta é uma solução compatível com POSIX.
fonte