Como escrever um script que aceite entrada de um arquivo ou do stdin?

57

Como alguém pode escrever um script que aceite entrada de um argumento de nome de arquivo ou de stdin?

por exemplo, você poderia usar lessdessa maneira. um pode executar less filenamee equivalentemente cat filename | less.

existe uma maneira fácil e imediata de fazer isso? ou preciso reinventar a roda e escrever um pouco de lógica no script?

Gilad Hoch
fonte
@PlasmaPower Desde que a pergunta esteja no tópico da SU, não há necessidade de perguntar em um site SE diferente. Muitos sites SE se sobrepõem; geralmente, não precisamos sugerir um site sobreposto, a menos que a pergunta seja fora do tópico (nesse caso, vote para migrar) ou no tópico, mas não receba muita resposta (nesse caso, o solicitante deve sinalizar para moderador- atenção / migração, não entre postagens).
Bob
Relacionado: Como ler um arquivo ou stdin no bash? em SO
kenorb

Respostas:

59

Se o argumento do arquivo for o primeiro argumento para o seu script, teste se existe um argumento ( $1) e se é um arquivo. Caso contrário, leia a entrada de stdin -

Portanto, seu script pode conter algo como isto:

#!/bin/bash
[ $# -ge 1 -a -f "$1" ] && input="$1" || input="-"
cat $input

por exemplo, então você pode chamar o script como

./myscript.sh filename

ou

who | ./myscript.sh

Editar Algumas explicações do script:

[ $# -ge 1 -a -f "$1" ]- Se pelo menos um argumento da linha de comando ( $# -ge 1) AND (-a operator) o primeiro argumento for um arquivo (-f testa se "$ 1" é um arquivo), o resultado do teste é verdadeiro.

&&é o operador lógico AND do shell. Se teste for verdadeiro, atribua input="$1"e cat $inputexibirá o arquivo.

||é o operador OR lógico do shell. Se o teste for falso, os comandos a seguir ||serão analisados. entrada é atribuída a "-". O comando cat -lê no teclado.

Resumindo, se o argumento do script for fornecido e for um arquivo, a entrada variável será atribuída ao nome do arquivo. Se não houver argumento válido, o gato lê no teclado.

suspeito
fonte
o que && input="$1" || input="-" faz e por que está fora do testoperador?
Cmo
Adicionei uma edição com algumas explicações que, espero, ajudem.
#Re
E se o script tiver vários argumentos ( $@)?
precisa saber é o seguinte
12

readlê da entrada padrão. Redirecioná-lo do arquivo ( ./script <someinput) ou através do pipe ( dosomething | ./script) não fará com que funcione de maneira diferente.

Tudo o que você precisa fazer é percorrer todas as linhas da entrada (e isso não difere da iteração nas linhas do arquivo).

(código de amostra, processa apenas uma linha)

#!/bin/bash

read var
echo $var

Irá ecoar a primeira linha da sua entrada padrão (através <ou |).

Lukasz Daniluk
fonte
obrigado! eu escolhi a outra resposta porque me convinha melhor. estava embrulhando outro script e não queria fazer loop até que todas as entradas recebidas (poderiam ser muitas entradas ... seriam um desperdício).
Gilad hoch
4

Você não menciona qual shell planeja usar, portanto, assumirei o bash, embora essas sejam coisas bastante comuns entre os shells.

Argumentos de arquivo

Os argumentos podem ser acessados ​​através das variáveis $1- $n( $0retorna o comando usado para executar o programa). Digamos que eu tenha um script que catexiba n número de arquivos com um delimitador entre eles:

#!/usr/bin/env bash
#
# Parameters:
#    1:   string delimiter between arguments 2-n
#    2-n: file(s) to cat out
for arg in ${@:2} # $@ is the array of arguments, ${@:2} slices it starting at 2.
do
   cat $arg
   echo $1
done

Nesse caso, estamos passando um nome de arquivo para cat. No entanto, se você quiser transformar os dados no arquivo (sem gravá-los e gravá-los explicitamente), também poderá armazenar o conteúdo do arquivo em uma variável:

file_contents=$(cat $filename)
[...do some stuff...]
echo $file_contents >> $new_filename

Leia de stdin

Quanto à leitura do stdin, a maioria dos shells possui um padrão bastante readembutido, embora existam diferenças na forma como os prompts são especificados (no mínimo).

A página de manual do Bash builtins tem uma explicação bastante concisa read, mas eu prefiro a página do Bash Hackers .

Simplesmente:

read var_name

Várias variáveis

Para definir várias variáveis, basta fornecer vários nomes de parâmetros para read:

read var1 var2 var3

read colocará uma palavra de stdin em cada variável, despejando todas as palavras restantes na última variável.

λ read var1 var2 var3
thing1 thing2 thing3 thing4 thing5
λ echo $var1; echo $var2; echo $var3
thing1
thing2
thing3 thing4 thing5

Se menos palavras forem inseridas do que variáveis, as demais variáveis ​​ficarão vazias (mesmo se definidas anteriormente):

λ read var1 var2 var3
thing1 thing2
λ echo $var1; echo $var2; echo $var3
thing1
thing2
# Empty line

Comandos

Eu uso -pfrequentemente o sinalizador para um prompt:

read -p "Enter filename: " filename

Nota: ZSH e KSH (e talvez outros) usam uma sintaxe diferente para avisos:

read "filename?Enter filename: " # Everything following the '?' is the prompt

Valores padrão

Isso não é realmente um readtruque, mas eu o uso muito em conjunto com read. Por exemplo:

read -p "Y/[N]: " reply
reply=${reply:-N}

Basicamente, se a variável (resposta) existir, retorne a si mesma, mas se estiver vazia, retorne o seguinte parâmetro ("N").

Jacob Hayes
fonte
4

A maneira mais simples é redirecionar você mesmo:

if [ "$1" ] ; then exec < "$1" ; fi

Ou se você preferir a forma mais concisa:

test "$1" && exec < "$1"

Agora, o restante do seu script pode ser lido no stdin. É claro que você pode fazer o mesmo com a análise de opções mais avançadas, em vez de codificar a posição do nome do arquivo como "$1".

R ..
fonte
exectentará executar o argumento como um comando que não é o que queremos aqui.
Suzana
@ Suzana_K: Não quando não tem argumentos, como aqui. Nesse caso, ele apenas substitui os descritores de arquivo do próprio shell, em vez de um processo filho.
R ..
Copiei if [ "$1" ] ; then exec < "$1" ; fiem um script de teste e ele fornece uma mensagem de erro porque o comando é desconhecido. Mesmo com a forma concisa.
Suzana
11
@Suzana_K: Qual shell você está usando? Se isso for verdade, não é uma implementação funcional do comando sh POSIX / shell Bourne.
R ..
GNU bash 4.3.11 no Linux Mint Qiana
Suzana
3

use (ou encadeie) algo que já se comporte dessa maneira e use "$@"

digamos que eu quero escrever uma ferramenta que substitua a execução de espaços no texto por guias

tré a maneira mais óbvia de fazer isso, mas ela aceita stdin, por isso temos que nos encadear cat:

$ cat entab1.sh
#!/bin/sh

cat "$@"|tr -s ' ' '\t'
$ cat entab1.sh|./entab1.sh
#!/bin/sh

cat     "$@"|tr -s      '       '       '\t'
$ ./entab1.sh entab1.sh
#!/bin/sh

cat     "$@"|tr -s      '       '       '\t'
$ 

por exemplo, em que a ferramenta usada já se comporta dessa maneira, poderíamos reimplementá-lo com sed:

$ cat entab2.sh
#!/bin/sh

sed -r 's/ +/\t/g' "$@"
$ 
Aaron Davies
fonte
3

Você também pode fazer:

#!/usr/bin/env bash

# Set variable input_file to either $1 or /dev/stdin, in case $1 is empty
# Note that this assumes that you are expecting the file name to operate on on $1
input_file="${1:-/dev/stdin}"

# You can now use "$input_file" as your file to operate on
cat "$input_file"

Para obter mais truques de substituição de parâmetros no Bash, consulte isso .

Daniel
fonte
11
Isto é fantástico! Estou usando uglifyjs < /dev/stdine funciona maravilhosamente!
fregante
0

Você também pode simplificar e usar esse código


Quando você cria um arquivo de script pass_it_on.sh com este código,

#!/bin/bash

cat

Você pode correr

cat SOMEFILE.txt | ./pass_it_on.sh

e todo o conteúdo do stdin será simplesmente expelido para a tela.


Como alternativa, use esse código para manter uma cópia do stdin em um arquivo e depois enviá-lo para a tela.

#!/bin/bash

tmpFile=`mktemp`
cat > $tmpFile
cat $tmpFile    

e aqui está outro exemplo, talvez mais legível, explicado aqui:

http://mockingeye.com/blog/2013/01/22/reading-everything-stdin-in-a-bash-script/

#!/bin/bash

VALUE=$(cat)

echo "$VALUE"

Diverta-se.

RaamEE

RaamEE
fonte
0

A maneira mais simples e compatível com POSIX é:

file=${1--}

que é equivalente a ${1:--}.

Em seguida, leia o arquivo como de costume:

while IFS= read -r line; do
  printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")
kenorb
fonte