Respostas:
A solução a seguir lê de um arquivo se o script for chamado com um nome de arquivo como o primeiro parâmetro, $1
caso contrário, da entrada padrão.
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
A substituição ${1:-...}
leva, $1
se definida, caso contrário, o nome do arquivo da entrada padrão do próprio processo é usado.
/proc/$$/fd/0
e /dev/stdin
? Notei que o último parece ser mais comum e parece mais direto.
-r
ao seu read
comando, para que ele não coma acidentalmente \
caracteres; use while IFS= read -r line
para preservar os espaços em branco iniciais e finais.
/bin/sh
- você está usando um shell diferente de bash
ou sh
?
Talvez a solução mais simples seja redirecionar stdin com um operador de redirecionamento de mesclagem:
#!/bin/bash
less <&0
Stdin é o descritor de arquivo zero. O exemplo acima envia a entrada canalizada para o seu script bash para o stdin de less.
Leia mais sobre o redirecionamento de descritor de arquivo .
<&0
nesta situação - seu exemplo funcionará da mesma forma com ou sem ela - aparentemente, as ferramentas que você chama de dentro de um script bash por padrão veem o mesmo stdin que o próprio script (a menos que o script o consuma primeiro).
Aqui está a maneira mais simples:
#!/bin/sh
cat -
Uso:
$ echo test | sh my_script.sh
test
Para atribuir stdin à variável, você pode usar: STDIN=$(cat -)
ou simplesmente STDIN=$(cat)
como o operador não é necessário (conforme o comentário @ mklement0 ).
Para analisar cada linha da entrada padrão , tente o seguinte script:
#!/bin/bash
while IFS= read -r line; do
printf '%s\n' "$line"
done
Para ler do arquivo ou stdin (se o argumento não estiver presente), você pode estendê-lo para:
#!/bin/bash
file=${1--} # POSIX-compliant; ${1:--} can be used either.
while IFS= read -r line; do
printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")
Notas:
-
read -r
- Não trate um caractere de barra invertida de maneira especial. Considere cada barra invertida como parte da linha de entrada.- Sem configuração
IFS
, por padrão, as seqüências Spacee Tabno início e no final das linhas são ignoradas (aparadas).- Use em
printf
vez deecho
para evitar a impressão de linhas vazias quando a linha consistir em uma única-e
,-n
ou-E
. No entanto, há uma solução alternativa usando oenv POSIXLY_CORRECT=1 echo "$line"
que executa seu GNU externoecho
que o suporta. Veja: Como eco "-e"?
Veja: Como ler stdin quando nenhum argumento é passado?no stackoverflow SE
[ "$1" ] && FILE=$1 || FILE="-"
para FILE=${1:--}
. (Quibble: melhor evitar todos os-maiúsculas shell variáveis para colisões de nomes evitar com ambiente variáveis.)
${1:--}
é compatível com POSIX, portanto deve funcionar em todos os shells semelhantes a POSIX. O que não funcionará em todos esses shells é a substituição do processo ( <(...)
); funcionará no bash, ksh, zsh, mas não no traço, por exemplo. Além disso, é melhor adicionar -r
ao seu read
comando, para que ele não coma acidentalmente \
caracteres; preceda IFS=
para preservar os espaços em branco iniciais e finais.
echo
: se uma linha consiste em -e
, -n
ou -E
, ela não será mostrada. Para corrigir isso, você deve usar printf
: printf '%s\n' "$line"
. Eu não o incluí na minha edição anterior ... muitas vezes minhas edições são revertidas quando eu corrigo esse erro :(
.
--
é inútil se primeiro argumento é'%s\n'
IFS=
com read
e printf
não echo
. :)
.
Eu acho que este é o caminho direto:
$ cat reader.sh
#!/bin/bash
while read line; do
echo "reading: ${line}"
done < /dev/stdin
-
$ cat writer.sh
#!/bin/bash
for i in {0..5}; do
echo "line ${i}"
done
-
$ ./writer.sh | ./reader.sh
reading: line 0
reading: line 1
reading: line 2
reading: line 3
reading: line 4
reading: line 5
read
lê de stdin por padrão , então não há nenhuma necessidade para < /dev/stdin
.
A echo
solução adiciona novas linhas sempre que IFS
interrompe o fluxo de entrada. A resposta do @ fgm pode ser modificada um pouco:
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
read
's comportamento: enquanto read
que potencialmente dividida em várias fichas pelos caracteres. contido em $IFS
, ele retornará apenas um único token se você especificar apenas um único nome de variável (mas, por padrão, apara e espaços em branco à esquerda e à direita).
read
e $IFS
- echo
ele próprio adiciona novas linhas sem a -n
bandeira. "O utilitário echo grava todos os operandos especificados, separados por caracteres em branco (` ') e seguidos por um caractere de nova linha (`\ n') na saída padrão."
\n
adicionada por echo
: O Perl $_
inclui a linha que termina \n
na linha lida, enquanto o bash read
não. (No entanto, como @gniourf_gniourf aponta em outro lugar, a abordagem mais robusta é usar printf '%s\n'
em vez disso echo
).
O loop Perl na pergunta lê de todos os os argumentos de nome de arquivo na linha de comando ou da entrada padrão se nenhum arquivo for especificado. As respostas que vejo parecem processar um único arquivo ou entrada padrão se não houver um arquivo especificado.
Embora muitas vezes ridicularizado com precisão como UUOC (uso inútil de cat
), há momentos em que cat
é a melhor ferramenta para o trabalho e é discutível que este seja um deles:
cat "$@" |
while read -r line
do
echo "$line"
done
A única desvantagem disso é que ele cria um pipeline em execução em um sub-shell, para que coisas como atribuições de variáveis no while
loop não sejam acessíveis fora do pipeline. A bash
maneira de contornar isso é Substituição de Processo :
while read -r line
do
echo "$line"
done < <(cat "$@")
Isso deixa o while
loop em execução no shell principal, para que as variáveis definidas no loop sejam acessíveis fora do loop.
>>EOF\n$(cat "$@")\nEOF
. Finalmente, uma queixa: while IFS= read -r line
é uma melhor aproximação do que while (<>)
faz no Perl (preserva os espaços em branco à esquerda e à direita - embora o Perl também mantenha a trilha \n
).
O comportamento de Perl, com o código fornecido no OP, pode receber nenhum ou vários argumentos, e se um argumento é um único hífen, -
isso é entendido como stdin. Além disso, sempre é possível ter o nome do arquivo $ARGV
. Nenhuma das respostas dadas até agora realmente imita o comportamento de Perl nesses aspectos. Aqui está uma possibilidade pura do Bash. O truque é usar exec
adequadamente.
#!/bin/bash
(($#)) || set -- -
while (($#)); do
{ [[ $1 = - ]] || exec < "$1"; } &&
while read -r; do
printf '%s\n' "$REPLY"
done
shift
done
O nome do arquivo está disponível em $1
.
Se nenhum argumento for fornecido, definimos artificialmente -
como o primeiro parâmetro posicional. Em seguida, fazemos um loop nos parâmetros. Se um parâmetro não for -
, redirecionamos a entrada padrão do nome do arquivo com exec
. Se esse redirecionamento for bem-sucedido, fazemos um loop com um while
loop. Estou usando a REPLY
variável padrão e, neste caso, você não precisa redefinir IFS
. Se você quiser outro nome, deverá redefinir IFS
assim (a menos, é claro, que não queira e saiba o que está fazendo):
while IFS= read -r line; do
printf '%s\n' "$line"
done
Mais precisamente...
while IFS= read -r line ; do
printf "%s\n" "$line"
done < file
IFS=
e -r
ao read
comando garante que cada linha seja lida sem modificação (incluindo espaços em branco à esquerda e à direita).
Por favor, tente o seguinte código:
while IFS= read -r line; do
echo "$line"
done < file
read
sem IFS=
e -r
, e o pobre $line
sem suas citações saudáveis.
read -r
notação. OMI, POSIX entendeu errado; a opção deve ativar o significado especial para as barras invertidas à direita, não desativá-lo - para que os scripts existentes (antes da existência do POSIX) não fossem interrompidos porque o -r
foi omitido. Observo, no entanto, que fazia parte da IEEE 1003.2 1992, que foi a versão mais antiga do padrão de shell e utilitários POSIX, mas foi marcada como uma adição até então, portanto, isso é uma chatice sobre oportunidades antigas. Eu nunca tive problemas porque meu código não usa -r
; Eu devo ter sorte. Me ignore nisso.
-r
deve ser padrão. Concordo que é improvável que ocorra nos casos em que não usá-lo gera problemas. Porém, código quebrado é código quebrado. Minha edição foi desencadeada pela $line
variável pobre que perdeu muito as aspas. Eu consertei o read
tempo que estava nisso. Não corrigi o echo
porque esse é o tipo de edição que é revertida. :(
.
O código ${1:-/dev/stdin}
entenderá apenas o primeiro argumento, então, que tal isso.
ARGS='$*'
if [ -z "$*" ]; then
ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
echo "$line"
done
Não acho nenhuma dessas respostas aceitável. Em particular, a resposta aceita apenas lida com o primeiro parâmetro da linha de comando e ignora o restante. O programa Perl que ele está tentando emular manipula todos os parâmetros da linha de comando. Portanto, a resposta aceita nem mesmo responde à pergunta. Outras respostas usam extensões bash, adicionam comandos desnecessários 'cat', funcionam apenas no caso simples de ecoar a entrada na saída ou são apenas desnecessariamente complicados.
No entanto, tenho que dar crédito a eles, porque eles me deram algumas idéias. Aqui está a resposta completa:
#!/bin/sh
if [ $# = 0 ]
then
DEFAULT_INPUT_FILE=/dev/stdin
else
DEFAULT_INPUT_FILE=
fi
# Iterates over all parameters or /dev/stdin
for FILE in "$@" $DEFAULT_INPUT_FILE
do
while IFS= read -r LINE
do
# Do whatever you want with LINE here.
echo $LINE
done < "$FILE"
done
Combinei todas as respostas acima e criei uma função shell que atendesse às minhas necessidades. Isto é de um terminal cygwin das minhas 2 máquinas Windows10, onde eu tinha uma pasta compartilhada entre elas. Eu preciso ser capaz de lidar com o seguinte:
cat file.cpp | tx
tx < file.cpp
tx file.cpp
Onde um nome de arquivo específico é especificado, eu preciso usar o mesmo nome de arquivo durante a cópia. Onde o fluxo de dados de entrada foi canalizado, então eu preciso gerar um nome de arquivo temporário com a hora minutos e segundos. A pasta principal compartilhada possui subpastas dos dias da semana. Isso é para fins organizacionais.
Eis o script final para minhas necessidades:
tx ()
{
if [ $# -eq 0 ]; then
local TMP=/tmp/tx.$(date +'%H%M%S')
while IFS= read -r line; do
echo "$line"
done < /dev/stdin > $TMP
cp $TMP //$OTHER/stargate/$(date +'%a')/
rm -f $TMP
else
[ -r $1 ] && cp $1 //$OTHER/stargate/$(date +'%a')/ || echo "cannot read file"
fi
}
Se houver alguma maneira de otimizar ainda mais isso, eu gostaria de saber.
O seguinte funciona com o padrão sh
(Testado com dash
no Debian) e é bastante legível, mas isso é uma questão de gosto:
if [ -n "$1" ]; then
cat "$1"
else
cat
fi | commands_and_transformations
Detalhes: se o primeiro parâmetro não estiver vazio, cat
esse arquivo será cat
inserido como padrão. Em seguida, a saída de toda a if
instrução é processada pelo commands_and_transformations
.
cat "${1:--}" | any_command
. Ler as variáveis shell e repeti-las pode funcionar para arquivos pequenos, mas não é tão dimensionável.
[ -n "$1" ]
pode ser simplificado para [ "$1" ]
.
E se
for line in `cat`; do
something($line);
done
cat
será colocada na linha de comando. A linha de comando tem um tamanho máximo. Além disso, isso não será lido linha por linha, mas palavra por palavra.