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, $1caso contrário, da entrada padrão.
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
A substituição ${1:-...}leva, $1se definida, caso contrário, o nome do arquivo da entrada padrão do próprio processo é usado.
/proc/$$/fd/0e /dev/stdin? Notei que o último parece ser mais comum e parece mais direto.
-rao seu readcomando, para que ele não coma acidentalmente \ caracteres; use while IFS= read -r linepara preservar os espaços em branco iniciais e finais.
/bin/sh- você está usando um shell diferente de bashou 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 .
<&0nesta 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
printfvez deechopara evitar a impressão de linhas vazias quando a linha consistir em uma única-e,-nou-E. No entanto, há uma solução alternativa usando oenv POSIXLY_CORRECT=1 echo "$line"que executa seu GNU externoechoque 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 -rao seu readcomando, 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, -nou -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 reade printfnã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
readlê de stdin por padrão , então não há nenhuma necessidade para < /dev/stdin.
A echosolução adiciona novas linhas sempre que IFSinterrompe 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).
reade $IFS- echoele próprio adiciona novas linhas sem a -nbandeira. "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."
\nadicionada por echo: O Perl $_ inclui a linha que termina \nna linha lida, enquanto o bash readnã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 whileloop não sejam acessíveis fora do pipeline. A bashmaneira de contornar isso é Substituição de Processo :
while read -r line
do
echo "$line"
done < <(cat "$@")
Isso deixa o whileloop 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 execadequadamente.
#!/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 whileloop. Estou usando a REPLYvariável padrão e, neste caso, você não precisa redefinir IFS. Se você quiser outro nome, deverá redefinir IFSassim (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 readcomando 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
readsem IFS=e -r, e o pobre $linesem suas citações saudáveis.
read -rnotaçã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 -rfoi 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.
-rdeve 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 $linevariável pobre que perdeu muito as aspas. Eu consertei o readtempo que estava nisso. Não corrigi o echoporque 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 | txtx < file.cpptx file.cppOnde 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 dashno 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, catesse arquivo será catinserido como padrão. Em seguida, a saída de toda a ifinstruçã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
catserá 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.