Como capturar stdin em uma variável sem remover nenhuma nova linha à direita?

9

Em um script de shell ...

Como capturar stdin em uma variável sem remover nenhuma nova linha à direita?

Agora eu tentei:

var=`cat`
var=`tee`
var=$(tee)

Em todos os casos $var, não haverá a nova linha à direita do fluxo de entrada. Obrigado.

TAMBÉM: Se não houver uma nova linha à direita na entrada, a solução não deve adicionar uma .

ATUALIZAÇÃO À LUZ DA RESPOSTA ACEITADA:

A solução final que usei no meu código é a seguinte:

function filter() {
    #do lots of sed operations
    #see https://github.com/gistya/expandr for full code
}

GIT_INPUT=`cat; echo x`
FILTERED_OUTPUT=$(printf '%s' "$GIT_INPUT" | filter)
FILTERED_OUTPUT=${FILTERED_OUTPUT%x}
printf '%s' "$FILTERED_OUTPUT"

Se você deseja ver o código completo, consulte a página do github para expandr , um pequeno script de shell de filtro de expansão de palavra-chave git de código aberto que desenvolvi para fins de segurança da informação. De acordo com as regras configuradas nos arquivos .gitattributes (que podem ser específicos da ramificação) e no git config , o git canaliza cada arquivo através do script shell expandr.sh sempre que o fizer dentro ou fora do repositório. (É por isso que era fundamental preservar as novas linhas finais, ou a falta delas). Isso permite limpar informações confidenciais e trocar diferentes conjuntos de valores específicos do ambiente para teste, preparação e ramificações ativas.

CommaToast
fonte
o que você faz aqui não é necessário. filterleva stdin- corre sed. Você pega stdinem $GIT_INPUTseguida, imprimir que volta ao stdoutlongo de um tubo para filtere pegar o seu stdoutno $FILTERED_OUTPUTe, em seguida, imprimi-lo de volta para stdout. Todos os 4 linhas na parte inferior do seu exemplo acima pode ser substituído com apenas este: filter. Sem querer ofender aqui, é só ... você está trabalhando demais. Você não precisa das variáveis ​​do shell na maioria das vezes - basta direcionar a entrada para o lugar certo e repassá-la.
mikeserv
Não, o que eu faço aqui é necessário, porque se eu fizer filter, ele adicionará caracteres de nova linha ao final de qualquer fluxo de entrada que não tenha terminado em nova linha inicialmente. De fato, originalmente fiz isso, filtermas encontrei o problema que me levou a essa solução, porque nem "sempre adiciona novas linhas" nem "sempre tira novas linhas" são soluções aceitáveis.
CommaToast
sedprovavelmente fará a nova linha extra - mas você deve lidar com isso filternão com todo o resto. E todas essas funções que você tem basicamente fazem a mesma coisa - a sed s///. Você está usando o shell para canalizar os dados salvos em sua memória, para sedque sedpossam substituí- los por outros dados que o shell armazenou na memória para sedcanalizá-los de volta para o shell. Por que não apenas [ "$var" = "$condition" ] && var=new_value? Também não recebo as matrizes - você está armazenando o nome da matriz e [0]depois sedsubstituindo-a pelo valor [1]? Talvez conversar?
mikeserv
@ mikeserv - Qual seria o benefício de mover esse código para dentro filter? Funciona perfeitamente como está. A respeito de como o código no meu link funciona e por que eu o configurei da maneira que fiz, vamos falar sobre isso em uma sala de bate-papo.
CommaToast

Respostas:

7

As novas linhas à direita são removidas antes que o valor seja armazenado na variável Você pode querer fazer algo como:

var=`cat; echo x`

e use em ${var%x}vez de $var. Por exemplo:

printf "%s" "${var%x}"

Observe que isso resolve o problema de novas linhas à direita, mas não o byte nulo (se a entrada padrão não for texto), pois de acordo com a substituição do comando POSIX :

Se a saída contiver bytes nulos, o comportamento não será especificado.

Mas as implementações de shell podem preservar bytes nulos.

vinc17
fonte
Os arquivos de texto normalmente contêm bytes nulos? Não vejo por que eles fariam. Mas o script que você acabou de mencionar não parece funcionar.
CommaToast
@CommaToast Os arquivos de texto não contêm bytes nulos. Mas a questão diz apenas stdin / stream de entrada, que pode não ser texto no caso mais geral.
vinc17
ESTÁ BEM. Bem, eu tentei na linha de comando e não fez nada e, dentro do meu próprio script, sua sugestão falha porque adiciona "..." no final do arquivo. Além disso, se não houvesse nova linha lá, ela ainda adiciona uma.
CommaToast
@CommaToast O "..." foi apenas um exemplo. Eu esclareci minha resposta. Nenhuma nova linha é adicionada (consulte o texto antes do "..." no exemplo).
precisa saber é
1
Bem, conchas não devem esconder coisas, isso não é legal. Essas conchas devem ser disparadas. Não gosto quando meu computador pensa que é melhor que eu.
CommaToast
4

Você pode usar o readbuilt-in para fazer isso:

$ IFS='' read -d '' -r foo < <(echo bar)

$ echo "<$foo>"
<bar
>

Para um script ler STDIN, seria simplesmente:

IFS='' read -d '' -r foo

 

Não sei ao certo em que conchas isso funcionará. Mas funciona bem no bash e no zsh.

Patrick
fonte
Nem -da substituição do processo ( <(...)) é portátil; esse código não funcionará dash, por exemplo.
chepner
Bem, a substituição do processo não faz parte da resposta, isso foi apenas parte do exemplo que mostra que funciona. Quanto a -d, é por isso que coloco o aviso no final. O OP não especifica o shell.
Patrick Patrick
@chepner - enquanto o estilo difere um pouco, o conceito certamente funciona dash. Você apenas usa <<HEREDOC\n$(gen input)\nHEREDOC\n- in dash- que usa tubos para heredocs da mesma forma que outros reservatórios os utilizam para substituição de processos - não faz diferença. A read -dcoisa é apenas especificar um delimitador - você pode fazer o mesmo de uma dúzia de maneiras - apenas tenha certeza. Embora você precise de um pouco de atenção gen input.
mikeserv
Você define IFS = '' para que ele não coloque espaços entre as linhas que lê em hein? Truque legal.
CommaToast
Na verdade, nesse caso, IFS=''provavelmente não é necessário. É feito para que readnão colapsem espaços. Mas quando está lendo uma única variável, não tem efeito (que eu me lembre). Mas eu me sinto mais seguro deixá-la em :-)
Patrick
2

Você pode fazer como:

input | { var=$(sed '$s/$/./'); var=${var%.}; }

O que quer que você faça $vardesaparece assim que você sai desse { current shell ; }grupo de qualquer maneira. Mas também pode funcionar como:

var=$(input | sed '$s/$/./'); var=${var%.}
mikeserv
fonte
1
Note-se que nem sempre a primeira solução, ou seja, ter que ser usada $varno { ... }agrupamento. Por exemplo, se este comando for executado dentro de um loop e for necessário $varfora do loop.
precisa saber é
@ vinc17 - se é um loop que eu gostaria de usar, então eu o usaria no lugar das {}chaves. É verdade - e é explicitamente observado na resposta - que o valor de $varé muito provável que desapareça completamente quando o { current shell; }agrupamento é fechadas. Existe alguma maneira mais explícita de dizer isso do que o que você faz $vardesaparece ...?
mikeserv
@ vinc17 - provavelmente a melhor maneira, porém:input | sed "s/'"'/&"&"&/g;s/.*/process2 '"'-> &'/" | sh
mikeserv 9/09/14
1
Há também a _variablesfunção de bash_completion, que armazena o resultado de uma substituição de comando em uma variável global COMPREPLY. Se uma solução de pipeline fosse usada para manter novas linhas, o resultado seria perdido. Em sua resposta, temos a impressão de que ambas as soluções são igualmente boas. Além disso, deve-se notar que o comportamento da solução de pipeline depende muito do shell: um usuário pode testar echo foo | { var=$(sed '$s/$/./'); var=${var%.}; } ; echo $varcom ksh93 e zsh e acha que está OK, enquanto esse código está com erros.
vinc17
1
Você não disse "não funciona". Você acabou de dizer " $vardesaparece" (o que na verdade não é verdade, pois isso depende do shell - o comportamento não é especificado pelo POSIX), que é uma sentença bastante neutra. A segunda solução é melhor porque não sofre desse problema e seu comportamento é consistente em todos os shells POSIX.
precisa saber é