2009-12-31 20 views
22

stavo cercando di fare questo per decidere se reindirizzare stdin in un file oppure no:Bash: standard input in modo dinamico in uno script

[ ...some condition here... ] && input=$fileName || input="&0" 
./myScript < $input 

Ma questo non funziona, perché quando la variabile $ input è "& 0", bash lo interpreta come un nome file.

Tuttavia, ho potuto solo fare:

if [ ...condition... ];then 
    ./myScript <$fileName 
else 
    ./myScript 

Il problema è che ./myScript è in realtà una riga di comando lunga che io non voglio duplicare, né voglio creare una funzione per esso perché non è neanche così lungo (non ne vale la pena).

Poi venne in mente di fare questo:

[ ...condition... ] && input=$fileName || input= #empty 
cat $input | ./myScript 

Ma che richiede di eseguire un altro comando e un tubo (cioè una subshell).
C'è un altro modo più semplice ed efficiente?

+4

vale sempre la pena la creazione di funzioni. –

+2

Non dire mai "sempre". Le funzioni aggiungono un livello di astrazione che non è sempre desiderabile. – GetFree

+1

Forse. In questo caso penso che sia desiderabile. –

risposta

20

Prima di tutto stdin è il descrittore di file 0 (zero) anziché 1 (che è stdout).

È possibile duplicare descrittori di file o utilizzare i nomi di file in modo condizionale in questo modo:

[[ some_condition ]] && exec 3<$filename || exec 3<&0 

some_long_command_line <&3 
+0

Proprio quello di cui avevo bisogno. Se creo il nuovo descrittore di file all'interno di una funzione, sarà ancora presente al termine della funzione? – GetFree

+0

Sì, ma anche il puntatore del file. In altre parole, se cerchi la fine del file nella funzione, sarai comunque al di fuori della funzione. 'test() {exec 3

2

Come su

function runfrom { 
    local input="$1" 
    shift 
    case "$input" in 
     -) "[email protected]" ;; 
     *) "[email protected]" < "$input" ;; 
    esac 
} 

Ho usato il segno meno per indicare lo standard input, perché questo è tradizionale per molti programmi Unix.

Ora si scrive

[ ... condition ... ] && input="$fileName" || input="-" 
runfrom "$input" my-complicated-command with many arguments 

Trovo queste funzioni/comandi che prendono i comandi come argomenti (come xargs(1)) può essere molto utile, e compongono bene.

1

Uso eval:

#! /bin/bash 

[ $# -gt 0 ] && input="'"$1"'" || input="&1" 

eval "./myScript <$input" 

Questo semplice sostituto di myScript

#! /usr/bin/perl -lp 
$_ = reverse 

produce il seguente output:

 
$ ./myDemux myScript 
pl- lrep/nib/rsu/ !# 
esrever = _$ 

$ ./myDemux 
foo 
oof 
bar 
rab 
baz 
zab 

Nota che gestisce spazi ingressi troppo:

 
$ ./myDemux foo\ bar 
eman eht ni ecaps a htiw elif 

All'ingresso tubo verso il basso per myScript, utilizzare process substitution:

 
$ ./myDemux <(md5sum /etc/issue) 
eussi/cte/ 01672098e5a1807213d5ba16e00a7ad0 

Si noti che se si tenta di inviare l'output direttamente, come in

 
$ md5sum /etc/issue | ./myDemux 

si bloccherà in attesa di input dal terminale, mentre ephemient's answer non ha questa mancanza.

Un leggero cambiamento produce il comportamento desiderato:

#! /bin/bash 

[ $# -gt 0 ] && input="'"$1"'" || input=/dev/stdin 
eval "./myScript <$input" 
+0

Qual è la differenza tra' comando1 <(comando2) ',' comando1 <<< $ (comando2) 'e' comando2 | command1' ?? – GetFree

+0

Il primo è la sostituzione del processo, la cui documentazione è collegata in precedenza. Il secondo compone la sostituzione di comando ('$ (...)') con here-string ('<<<') per inviare l'output di 'command2' allo standard input di' command1'. L'ultimo passa attraverso la consueta sequenza fork-exec per l'esecuzione di comandi, ma crea una pipe la cui fine di scrittura sostituisce l'output standard di 'command2' e la cui estremità di lettura sostituisce l'input standard di' command1'. –

2

Se si sta attenti, si può usare 'eval' e la vostra prima idea.

[ ...some condition here... ] && input=$fileName || input="&1" 
eval ./myScript < $input 

Tuttavia, si dice che "myScript" è in realtà una complessa chiamata di comando; se coinvolge argomenti che potrebbero contenere spazi, allora devi stare molto attento prima di decidere di usare "eval".

Francamente, la preoccupazione per il costo di un comando "cat" probabilmente non vale la pena; è improbabile che sia il collo di bottiglia.

Ancora meglio è quello di progettare myScript in modo che funziona come un filtro normale Unix - si legge dallo standard input a meno che non si è dato uno o più file di lavoro (come, ad esempio, o catgrep come esempi). Quel design è basato su una lunga e solida esperienza - e quindi vale la pena emulare per evitare di dover affrontare problemi come questo.

7
(
    if [ ...some condition here... ]; then 
     exec <$fileName 
    fi 
    exec ./myscript 
) 

In una sottochiave, reindirizzare condizionatamente stdin ed eseguire lo script.

+3

+1. Metti virgolette su '$ fileName' nel caso contenga spazi. –

4

di input standard può essere rappresentato anche dal file di dispositivo speciale /dev/stdin, in modo da utilizzare che come un nome di file funzionerà.

file="/dev/stdin" 
./myscript < "$file" 
+0

Viene visualizzato un errore di "autorizzazione negata" quando non sono root. – GetFree

+1

Sembra possibilmente una sorta di errata configurazione; '/ dev/stdin' dovrebbe (quasi) essere sempre utilizzabile. In particolare, credo che Linux e Solaris lo implementino entrambi come un link simbolico a '/ proc/self/fd/0', e l'unico problema che posso pensare sarebbe se l'UID fosse cambiato. – ephemient

+0

Bene, quindi una situazione "comune" sarebbe: il terminale è di proprietà dell'utente A, lo script è in esecuzione come utente B, lo stdin è legato al terminale. E 'questo quello che stai facendo? – ephemient

0

persone mostrano a voi gli script molto lunghe, ma .... si ottiene trappola bash :) È necessario citare il tutto in bash. ad esempio, si desidera un file di elenco denominato & 0.

nome file = '& 0' #right ls $ nomefile # errore! questo sostituto $ nomefile e interpretare ls "$ nomefile" #right

altro, file con spazi.

nomefile = 'un po' di file con spazi' ls $ filename #wrong, bash tagliare primo e l'ultimo spazio, e ridurre gli spazi multipli tra con struttura parole ls "$ nomefile" righ

lo stesso è in il tuo copione si prega di cambiare:

./myScript < $input 

a

./myScript < "$input" 

il suo tutto. bash ha più trappole. Suggerisco di fare quotazione per "$ file" con lo stesso motivo. spazi e altri personaggi che possono essere interpretati sono sempre problemi.

ma che dire di/dev/stdin? questo è utilizzabile solo quando hai reindirizzato lo stdin e vuoi stampare qualcosa su vero stdin.

modo, lo script dovrebbe mostrare come questo:

[ ...some condition here... ] && input="$fileName" || input="&0" 
./myScript < "$input" 
Problemi correlati