¿Cómo ejecutar la salida de un comando dentro del shell actual?

89

Soy consciente de la utilidad source(también conocida como .), que tomará el contenido de un archivo y lo ejecutará dentro del shell actual.

Ahora, estoy transformando texto en comandos de shell y luego ejecutándolos, de la siguiente manera:

$ ls | sed ... | sh

lses solo un ejemplo aleatorio, el texto original puede ser cualquier cosa. sedtambién, solo un ejemplo para transformar texto. Lo interesante es sh. Canalizo lo que sea que llegue shy lo ejecuta.

Mi problema es que eso significa iniciar un nuevo sub shell. Prefiero que los comandos se ejecuten dentro de mi shell actual. Como podría hacer con source some-file, si tuviera los comandos en un archivo de texto.

No quiero crear un archivo temporal porque se siente sucio.

Alternativamente, me gustaría iniciar mi sub shell con exactamente las mismas características que mi shell actual.

actualizar

Ok, las soluciones que usan comillas invertidas ciertamente funcionan, pero a menudo necesito hacer esto mientras reviso y cambio la salida, así que preferiría si hubiera una manera de canalizar el resultado a algo al final.

triste actualización

Ah, la /dev/stdincosa se veía tan bonita, pero, en un caso más complejo, no funcionó.

Entonces, tengo esto:

find . -type f -iname '*.doc' | ack -v '\.doc$' | perl -pe 's/^((.*)\.doc)$/git mv -f $1 $2.doc/i' | source /dev/stdin

Lo que garantiza que todos los .docarchivos tengan su extensión en minúsculas.

Y que, dicho sea de paso, se puede manejar xargs, pero eso es más allá del punto.

find . -type f -iname '*.doc' | ack -v '\.doc$' | perl -pe 's/^((.*)\.doc)$/$1 $2.doc/i' | xargs -L1 git mv

Entonces, cuando ejecuto el primero, saldrá de inmediato, no pasa nada.

kch
fuente
¿Su comando complejo funciona cuando primero canaliza a un archivo temporal y luego lo genera? Si no es así, ¿cuál es el problema con la salida generada? La salida de su comando no funcionará si sus nombres de archivo tienen espacios o si ciertas secuencias no se escapan correctamente. Me gustaría agregar cotizaciones alrededor de $ 1 y $ 2.doc como mínimo.
Kaleb Pederson
¿Hay alguna buena razón para tener que ejecutar esto en el shell original? - estos ejemplos no manipulan el shell actual, por lo que no gana nada al hacerlo. La solución más rápida es redirigir la salida a un archivo y fuente de ese archivo aunque
nos
@kaleb la salida funciona bien. en este caso particular, incluso si me pido a sh. los nombres de los archivos no tienen espacio suficiente, pero gracias por señalarlo. @nos git variables de entorno en el shell original. y nuevamente, estos son solo ejemplos. la pregunta es de por vida.
kch
source / dev / stdin no funcionó para mí cuando necesitaba variables asignadas para quedarse. geirha en freenode bash me señaló mywiki.wooledge.org/BashFAQ/024 y sugirió que probara una fuente de sustitución de proceso <(comando) que funcionó para mí
srcerer

Respuestas:

85
$ ls | sed ... | source /dev/stdin

ACTUALIZACIÓN: Esto funciona en bash 4.0, así como en tcsh y dash (si cambia sourcea .). Aparentemente, esto tenía errores en bash 3.2. De las notas de la versión de bash 4.0 :

Se corrigió un error que causaba '.' fallar al leer y ejecutar comandos de archivos no regulares como dispositivos o canalizaciones con nombre.

mark4o
fuente
Okay. Entonces, a bash 4.0.
kch
Ah, y dado que está enumerando los shells, también funciona en zsh.
kch
1
Pensé que esto era ingenioso hasta que lo probé en msys / mingw (¡donde no hay carpeta / dev (o incluso dispositivos)! Probé muchas combinaciones de eval, $ () y comillas invertidas ''. No pude hacer que nada funcionara , así que finalmente redirigí la salida a un archivo temporal, obtuve el archivo temporal y lo eliminé. Básicamente "sed ...> /tmp/$$.tmp &&. /tmp/$$.tmp && rm / tmp / $$. tmp ". ¿Alguien tiene una solución msys / mingw sin archivos temporales ???
chriv
10
Solo un comentario: este no parece manejar las declaraciones de exportación como se esperaba. Si la cadena canalizada tiene una declaración de exportación, la variable exportet no está disponible en su entorno de terminal posteriormente, mientras que con eval export también funciona perfectamente.
Phil
1
Dado que MacOS X generalmente tiene bash 3.2 (¿tal vez Yosemite tiene 4.0?), Y la necesidad de trucos de lastpipe en mi caso de uso (exportar todas las variables en un archivo procesando previamente cada línea con sed para agregar 'exportar') elegí para ir con el eval "$(sed ...)"enfoque, pero gracias por el aviso de error 3.2.
Alex Dupuy
133

El evalcomando existe para este mismo propósito.

eval "$( ls | sed... )"

Más del manual de bash :

eval

          eval [arguments]

Los argumentos se concatenan juntos en un solo comando, que luego se lee y ejecuta, y su estado de salida se devuelve como el estado de salida de eval. Si no hay argumentos o solo argumentos vacíos, el estado de retorno es cero.

Juliano
fuente
8
El único problema aquí es que es posible que deba insertar; para separar sus comandos. Yo mismo utilizo este método para trabajar en AIX, Sun, HP y Linux.
Tanktalus
Tanktalus, gracias por ese comentario, hizo que mi guión funcionara. En mi máquina, eval no separa los comandos en las líneas nuevas y el uso de la fuente no funciona incluso con punto y coma. Eval con punto y coma es la solución. Ojalá pudiera darle algunos puntos.
Milan Babuškov
2
@ MilanBabuškov: Lo clasifiqué para ti ;-)
Phil
3
Esto es excelente. Sin embargo, para mi caso de uso, terminé teniendo que poner comillas alrededor de mi $ (), así: eval "$( ssh remote-host 'cat ~/.bash_profile' )"Tenga en cuenta que de hecho estoy usando bash 3.2.
Zachary Murray
3
Esto funciona para mí donde la respuesta aceptada no lo hizo.
Dan Tenenbaum
37

Vaya, sé que esta es una pregunta antigua, pero recientemente me encontré con el mismo problema exacto (así es como llegué aquí).

De todos modos, no me gusta la source /dev/stdinrespuesta, pero creo que encontré una mejor. En realidad, es engañosamente simple:

echo ls -la | xargs xargs

Bien, ¿verdad? En realidad, esto todavía no hace lo que quieres, porque si tienes varias líneas, las concatena en un solo comando en lugar de ejecutar cada comando por separado. Entonces la solución que encontré es:

ls | ... | xargs -L 1 xargs

la -L 1opción significa que usa (como máximo) 1 línea por ejecución de comando. Nota: si su línea termina con un espacio al final, ¡se concatenará con la siguiente línea! Así que asegúrese de que cada línea termine con un no espacio.

Finalmente, puedes hacer

ls | ... | xargs -L 1 xargs -t

para ver qué comandos se ejecutan (-t es detallado).

¡Espero que alguien lea esto!

rabensky
fuente
30

Intente usar la sustitución de procesos , que reemplaza la salida de un comando con un archivo temporal que luego se puede obtener:

source <(echo id)
rishta
fuente
7
¿Qué tipo de brujería es esta?
paulotorrens
Ese fue mi primer pensamiento también. Aunque me gusta más la solución eval. @PauloTorrens <(xyz) simplemente ejecuta xyz y reemplaza <(xyz) con el nombre de un archivo que escribirá la salida de xyz. Es realmente fácil entender cómo funciona esto haciendo, por ejemplo:, echo <(echo id)dando la salida /dev/fd/12(12 es un ejemplo) cat <(echo id), dando la salida idy luego source <(echo id)dando la misma salida que simplemente escribiendoid
netigger
2
@PauloTorrens, esto se llama sustitución de procesos . Consulte los documentos vinculados para obtener una explicación oficial, pero la respuesta corta es que "<()" es una sintaxis especial que fue diseñada para casos como este en mente.
Mark Stosberg
1
@MarkStosberg, ¿sabe si se trata de una sintaxis especial o simplemente de una subcapa (...)redirigida?
paulotorrens
2
@PauloTorrens, es una sintaxis especial solo para sustitución de procesos.
Mark Stosberg
6

Creo que esta es "la respuesta correcta" a la pregunta:

ls | sed ... | while read line; do $line; done

Es decir, se puede canalizar en un whilebucle; el readcomando comando toma una línea de su stdiny la asigna a la variable $line. $lineluego se convierte en el comando ejecutado dentro del ciclo; y continúa hasta que no haya más líneas en su entrada.

Esto todavía no funcionará con algunas estructuras de control (como otro bucle), pero encaja perfectamente en este caso.

Geoff Nixon
fuente
5
`ls | sed ...`

En cierto modo me siento como ls | sed ... | source -iba a ser más bonita, pero por desgracia sourceno entiende -para significar stdin.

caos
fuente
después de ver la respuesta de mark4o, ¿no se siente como si estuviera en nuestras caras todo este tiempo?
kch
Je, sí. Nunca recuerdo que esas cosas existan.
caos
1

Creo que su solución es la sustitución de comandos con comillas inversas: http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html

Ver sección 3.4.5

Eric Wendelin
fuente
3
Si está utilizando bash, la $( )sintaxis puede ser la preferida. 'Las
comillas invertidas del
6
$ (comando) y $ ((1 + 1)) funcionan en todos los shells posix. Creo que muchos se desaniman porque vim los marca como errores de sintaxis, pero eso es solo porque vim está resaltando para el shell Bourne original que muy pocos usan. Para que vim resalte correctamente, coloque esto en su .vimrc: deje g: is_posix = 1
pixelbeat
1

Para usar la solución de mark4o en bash 3.2 (macos), se puede usar una cadena aquí en lugar de tuberías como en este ejemplo:

. /dev/stdin <<< "$(grep '^alias' ~/.profile)"
ish-west
fuente
o use comillas invertidas:. / dev / stdin <<< `grep '^ alias' ~ / .profile`
William H. Hooper
0

¿Por qué no usar sourceentonces?

$ ls | sed ... > out.sh ; source out.sh
Kaleb Pederson
fuente
Mencionó que los archivos temporales son asquerosos.
caos
Oh sí, me perdí eso :(.
Kaleb Pederson