Come scrivere uno script che accetta input da un file o da stdin?


57

Come si può scrivere uno script che accetta input da un argomento di nome file o da stdin?

per esempio, potresti usare in lessquesto modo. si può eseguire less filenameed equivalentemente cat filename | less.

esiste un modo semplice "out of the box" per farlo? o devo reinventare la ruota e scrivere un po 'di logica nello script?


@PlasmaPower Finché la domanda è in argomento su SU, non è necessario fare una domanda su un sito SE diverso. Molti siti SE si sovrappongono; in genere non è necessario suggerire un sito sovrapposto a meno che la domanda non sia off-topic (nel qual caso, vota per migrare) o sull'argomento ma non ottenga molta risposta (nel qual caso, il richiedente dovrebbe contrassegnare come moderatore- attenzione / migrazione, non cross-post).
Bob,

Risposte:


59

Se l'argomento file è il primo argomento del tuo script, verifica che sia presente un argomento ( $1) e che sia un file. Altrimenti leggi l'input da stdin -

Quindi la tua sceneggiatura potrebbe contenere qualcosa del genere:

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

ad esempio, puoi chiamare lo script come

./myscript.sh filename

o

who | ./myscript.sh

Modifica Alcune spiegazioni dello script:

[ $# -ge 1 -a -f "$1" ]- Se almeno un argomento della riga di comando ( $# -ge 1) AND (operatore -a) il primo argomento è un file (-f verifica se "$ 1" è un file), il risultato del test è vero.

&&è l'operatore logico AND della shell. Se test è vero, quindi assegnare input="$1"e cat $inputverrà emesso il file.

||è l'operatore logico OR della shell. Se il test è falso, ||vengono analizzati i seguenti comandi . l'ingresso è assegnato a "-". Il comando cat -legge dalla tastiera.

Riassumendo, se viene fornito l'argomento script ed è un file, l'input della variabile viene assegnato al nome del file. Se non esiste un argomento valido, cat legge dalla tastiera.


cosa fa && input="$1" || input="-" e perché è al di fuori testdell'operatore?
cmo

Ho aggiunto una modifica con qualche spiegazione che spero possa esserti utile.
suspectus,

Cosa succede se lo script ha più argomenti ( $@)?
g33kz0r,

12

readlegge dallo standard input. Il reindirizzamento da file ( ./script <someinput) o tramite pipe ( dosomething | ./script) non lo farà funzionare diversamente.

Tutto quello che devi fare è passare in rassegna tutte le righe in input (e non differisce dall'iterare sulle righe nel file).

(codice di esempio, elabora solo una riga)

#!/bin/bash

read var
echo $var

Fa eco alla prima riga dell'input standard (attraverso <o |).


Grazie! scelgo l'altra risposta perché mi si adatta meglio. stavo avvolgendo un altro script, e non volevo fare il loop fino a quando tutti gli input non fossero stati ricevuti (potrebbe essere un sacco di input ... sarebbe uno spreco).
Gilad Hoch,

4

Non dici quale shell intendi utilizzare, quindi suppongo bash, anche se queste sono cose piuttosto standard tra le shell.

Argomenti dei file

È possibile accedere agli argomenti tramite le variabili $1- $n( $0restituisce il comando utilizzato per eseguire il programma). Supponiamo di avere uno script che ha appena catpubblicato n numero di file con un delimitatore tra di loro:

#!/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

In questo caso, stiamo passando un nome di file a cat. Tuttavia, se si desidera trasformare i dati nel file (senza scriverli e riscriverli in modo esplicito), è possibile anche archiviare il contenuto del file in una variabile:

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

Leggi da stdin

Per quanto riguarda la lettura da stdin, la maggior parte delle shell ha un readbuiltin piuttosto standard , anche se ci sono differenze nel modo in cui vengono specificati i prompt (almeno).

La pagina man di Bash builtins ha una spiegazione piuttosto concisa di read, ma preferisco la pagina di Bash Hackers .

Semplicemente:

read var_name

Variabili multiple

Per impostare più variabili, basta fornire più nomi di parametri per read:

read var1 var2 var3

read inserirà quindi una parola da stdin in ciascuna variabile, scaricando tutte le parole rimanenti nell'ultima variabile.

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

Se vengono inserite meno parole delle variabili, le variabili rimanenti saranno vuote (anche se precedentemente impostate):

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

prompt

Uso -pflag spesso per un prompt:

read -p "Enter filename: " filename

Nota: ZSH e KSH (e forse altri) usano una sintassi diversa per i prompt:

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

Valori standard

Questo non è davvero un readtrucco, ma lo uso molto insieme a read. Per esempio:

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

Fondamentalmente, se la variabile (risposta) esiste, restituisce se stessa, ma se è vuota, restituisce il seguente parametro ("N").


4

Il modo più semplice è reindirizzare te stesso stdin:

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

O se preferisci la forma più concisa:

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

Ora il resto della tua sceneggiatura può solo leggere da stdin. Ovviamente puoi fare lo stesso con l'analisi delle opzioni più avanzate invece di codificare a fondo la posizione del nome file come "$1".


execproverà ad eseguire l'argomento come comando che non è quello che vogliamo qui.
Suzana,

@Suzana_K: non quando non ha argomenti, come qui. In tal caso sostituisce semplicemente i descrittori di file per la shell stessa anziché un processo figlio.
R ..

Ho copiato if [ "$1" ] ; then exec < "$1" ; fiin uno script di test e viene visualizzato un messaggio di errore perché il comando non è noto. Lo stesso con la forma concisa.
Suzana,

1
@Suzana_K: quale shell stai usando? Se questo è vero, non è un'implementazione funzionante del comando sh POSIX / shell Bourne.
R ..

GNU bash 4.3.11 su Linux Mint Qiana
Suzana,

3

usa (o allontana) qualcos'altro che già si comporta in questo modo e usa "$@"

diciamo che voglio scrivere uno strumento che sostituirà le esecuzioni di spazi nel testo con le schede

trè il modo più ovvio per farlo, ma accetta solo lo stdin, quindi dobbiamo concatenarci 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'
$ 

per un esempio in cui lo strumento utilizzato si comporta già in questo modo, potremmo sedinvece reimplementarlo con :

$ cat entab2.sh
#!/bin/sh

sed -r 's/ +/\t/g' "$@"
$ 

3

Puoi anche fare:

#!/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"

Per ulteriori trucchi per la sostituzione dei parametri in Bash, vedi questo .


1
È fantastico! Sto usando uglifyjs < /dev/stdine funziona meravigliosamente!
fregante,

0

Puoi anche mantenerlo semplice e utilizzare questo codice


Quando crei un file di script pass_it_on.sh con questo codice,

#!/bin/bash

cat

Puoi correre

cat SOMEFILE.txt | ./pass_it_on.sh

e tutto il contenuto di stdin verrà semplicemente diffuso sullo schermo.


In alternativa, utilizzare questo codice per conservare una copia di stdin in un file e poi inviarlo allo schermo.

#!/bin/bash

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

e qui c'è un altro esempio, forse più leggibile, spiegato qui:

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

#!/bin/bash

VALUE=$(cat)

echo "$VALUE"

Divertiti.

RaamEE


0

Il modo più semplice e conforme POSIX è:

file=${1--}

che equivale a ${1:--}.

Quindi leggi il file come al solito:

while IFS= read -r line; do
  printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.