2009-12-31 22 views
22

yo estaba tratando de hacer esto para decidir si se debe redirigir la entrada estándar a un archivo o no:Bash: redirigir la entrada estándar de forma dinámica en un script

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

Pero eso no funciona porque cuando la variable de entrada es $ "& 0", bash lo interpreta como un nombre de archivo.

Sin embargo, sólo podía hacer:

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

El problema es que ./myscript es en realidad una línea de comandos desde hace tiempo que no quiero duplicar, ni quiero crear una función para él porque tampoco es demasiado largo (no vale la pena).

Entonces se me ocurrió hacer esto:

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

Pero eso requiere ejecutar un comando más y un tubo (es decir, un subnivel).
¿Hay alguna otra manera que sea más simple y más eficiente?

+4

siempre vale la pena la creación de funciones. –

+2

Nunca diga "siempre". Las funciones agregan un nivel de abstracción que no siempre es deseable. – GetFree

+1

Quizás. En este caso, creo que es deseable. –

Respuesta

20

En primer lugar stdin es el descriptor de archivo 0 (cero) en lugar de 1 (que es stdout).

Puede duplicar los descriptores de fichero o utilizar nombres de archivos de forma condicional como esto:

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

some_long_command_line <&3 
+0

Justo lo que necesitaba. Si creo el nuevo descriptor de archivo dentro de una función, ¿seguirá existiendo después de que termine la función? – GetFree

+0

Sí, pero también lo hará el puntero al archivo. En otras palabras, si busca el final del archivo en la función, seguirá estando allí fuera de la función. 'test() {exec 3

2

¿Qué tal

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

He utilizado el signo menos para denotar la entrada estándar, porque eso es tradicional para muchos programas de Unix.

Ahora se escribe

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

Encuentro estas funciones/comandos que tienen comandos como argumentos (como xargs(1)) puede ser muy útil, y se componen también.

1

Uso eval:

#! /bin/bash 

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

eval "./myScript <$input" 

Este simple sustituto de myScript

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

produce el siguiente resultado:

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

$ ./myDemux 
foo 
oof 
bar 
rab 
baz 
zab 

Nota que maneja los espacios en los insumos también:

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

Para canalizar la entrada a myScript, utilice process substitution:

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

Tenga en cuenta que si se intenta canalizar la salida directamente, como en

 
$ md5sum /etc/issue | ./myDemux 

colgará esperando en la entrada de la terminal, mientras que ephemient's answer no tiene este inconveniente.

Un ligero cambio produce el comportamiento deseado:

#! /bin/bash 

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

¿Cuál es la diferencia entre' command1 <(command2) ',' command1 <<< $ (command2) 'y' command2 | command1' ?? – GetFree

+0

El primero es la sustitución de procesos, cuya documentación está vinculada anteriormente. El segundo compone la sustitución de comando ('$ (...)') con here-string ('<<<') para enviar la salida de 'command2' a la entrada estándar de' command1'. El último pasa por la secuencia fork-exec habitual para ejecutar comandos, pero crea un conducto cuyo extremo de escritura reemplaza la salida estándar de 'command2' y cuyo final de lectura reemplaza la entrada estándar de' command1'. –

2

Si usted tiene cuidado, puede utilizar 'eval' y su primera idea.

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

Sin embargo, usted dice que 'myScript' es en realidad una invocación compleja de comandos; si involucra argumentos que pueden contener espacios, debe tener mucho cuidado antes de decidir usar 'eval'.

Francamente, preocuparse por el costo de un comando 'cat' probablemente no valga la pena; es poco probable que sea el cuello de botella.

Aún mejor es diseñar myScript para que funcione como un filtro normal de Unix - se lee de la entrada estándar a menos que se le da uno o más archivos para trabajar (como, por ejemplo, cat o grep como ejemplos). Ese diseño se basa en una experiencia larga y sólida, y por lo tanto vale la pena emularlo para evitar tener que lidiar con problemas como este.

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

En una subcadena, redirija condicionalmente stdin y ejecute la secuencia de comandos.

+3

+1. Ponga comillas alrededor de '$ fileName' en caso de que contenga espacios. –

4

de entrada estándar también puede ser representado por el archivo de dispositivo especial /dev/stdin, por lo que el uso de un nombre de archivo como va a funcionar.

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

Aparece un error de "permiso denegado" cuando no soy root. – GetFree

+1

Eso suena como posiblemente algún tipo de configuración incorrecta; '/ dev/stdin' debería (casi) siempre ser utilizable. En particular, creo que Linux y Solaris lo implementan como un enlace simbólico a '/ proc/self/fd/0', y los únicos problemas que puedo pensar serían si el UID ha cambiado. – ephemient

+0

Bueno, entonces una situación "común" sería: la terminal es propiedad del usuario A, la secuencia de comandos se ejecuta como usuario B, stdin está vinculada a la terminal. ¿Es esto lo que estás haciendo? – ephemient

0

personas se presentan a que las secuencias de comandos muy largos, pero .... se obtiene trampa de fiesta :) Usted debe citar todo en bash. por ejemplo, desea un archivo de lista llamado & 0.

nombre_archivo = '& 0' #right ls $ filename #wrong! este sustituto $ filename e interpretar ls "$ filename" #derecho

otro, archivos con espacios.

archivo = 'algún archivo con espacios' ls #wrong $ archivo, golpe del corte primero y último espacio, y reducir los múltiples espacios entre palabras y espacios con ls '$ archivo' Righ

lo mismo es en tu guion por favor cambie:

./myScript < $input 

a

./myScript < "$input" 

su todo. bash tiene más trampas. Sugiero que haga una cita para "$ file" con la misma razón. espacios y otros personajes que pueden ser interpretados son siempre problemas.

pero ¿qué pasa con/dev/stdin? esto solo se puede usar cuando redirigió stdin y desea imprimir algo en stdin real.

así, el script debe mostrar como esto:

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