Ejecute dos comandos en un argumento (sin secuencias de comandos)

28

¿Cómo puedo ejecutar dos comandos en una entrada, sin escribir esa entrada dos veces?

Por ejemplo, el comando stat dice mucho sobre un archivo, pero no indica su tipo de archivo:

stat fileName

El comando de archivo, dice de qué tipo es un archivo:

file fileName

Puede realizar esto en una línea de esta manera:

stat fileName ; file fileName

Sin embargo, debe escribir el nombre de archivo dos veces.

¿Cómo puede ejecutar ambos comandos en la misma entrada (sin escribir la entrada o una variable de la entrada dos veces)?

En Linux, sé cómo canalizar salidas, pero ¿cómo canalizas las entradas?

Lonniebiz
fuente
14
Para ser claros, esos no son "entradas", son argumentos (también conocidos como parámetros). La entrada se puede canalizar a través de <(entrada del archivo al lado izquierdo) o |(entrada del flujo al lado derecho). Hay una diferencia
Ricitos
66
No es una respuesta a su pregunta, pero tal vez una respuesta a su problema: en bash, el yank-last-argcomando (atajo predeterminado Meta-.o Meta-_; a Metamenudo es la Alttecla izquierda ) copiará el último parámetro de la línea de comando anterior en el actual. Entonces, después de ejecutar stat fileName, escribe file [Meta-.]y no tiene que fileNamevolver a escribir eso . Yo siempre uso eso.
Dubu
2
Y la diferencia es eso <y >son redireccionamientos , no tuberías . Una tubería conecta dos procesos, mientras que la redirección simplemente reasigna stdin / stdout.
bsd
@bdowning: Sí, pero estás redirigiendo la tubería, ¿no? Puede redirigir el inicio de una tubería para que se lea desde un archivo en el disco, o puede redirigir el final de una tubería para que se escriba en un archivo en el disco. Todavía tubería. ;-)
James Haigh
Relacionado (pero más especializado): ¿Existe una línea única que me permita crear un directorio y moverme al mismo tiempo?
G-Man dice 'reinstalar a Monica' el

Respuestas:

22

Aquí hay otra forma:

$ stat filename && file "$_"

Ejemplo:

$ stat /usr/bin/yum && file "$_"
  File: ‘/usr/bin/yum’
  Size: 801         Blocks: 8          IO Block: 4096   regular file
Device: 804h/2052d  Inode: 1189124     Links: 1
Access: (0755/-rwxr-xr-x)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:rpm_exec_t:s0
Access: 2014-06-11 22:55:53.783586098 +0700
Modify: 2014-05-22 16:49:35.000000000 +0700
Change: 2014-06-11 19:15:30.047017844 +0700
 Birth: -
/usr/bin/yum: Python script, ASCII text executable

Eso funciona en bashy zsh. Eso también funciona mkshy dashsolo cuando es interactivo. En AT&T ksh, eso solo funciona cuando file "$_"está en una línea diferente de la que statestá.

Cuonglm
fuente
1
El !$uno no funcionará porque se !$expande hasta la última palabra del último evento del historial (por lo tanto, de la línea de comando ingresada anteriormente, no del comando stat).
Stéphane Chazelas
@ StéphaneChazelas: Oh, sí, mi error, lo borré.
Cuonglm
32

Probablemente voy a hacer que me golpeen los nudillos por esto, aquí hay una combinación hacky de expansión de brazalete bash y evaluación que parece hacer el truco

eval {stat,file}" fileName;"
iruvar
fuente
8
Lo siento, pero no puedo resistirme .
Andreas Wiese
2
la expansión de llaves se originó en csh, no bash. bashcopiaste desde ksh. Ese código funcionará en todos los csh, tcsh, ksh, zsh, fish y bash.
Stéphane Chazelas
1
Lástima que pierdas la capacidad de "tabular" (autocompletar) el fileName, debido a la cita.
Lonniebiz
Lo siento, pero no pude resistir bien
Tomd
lo siento, ¿cuál es el problema aquí eval? (refiriéndose a los comentarios principales)
user13107
28

Con zsh, puedes usar funciones anónimas:

(){stat $1; file $1} filename

Con eslambdas:

@{stat $1; file $1} filename

También puedes hacer:

{ stat -; file -;} < filename

(haciendo lo statprimero ya fileque actualizará el tiempo de acceso).

Lo haría:

f=filename; stat "$f"; file "$f"

sin embargo, para eso están las variables.

Stéphane Chazelas
fuente
3
{ stat -; file -;} < filenamees mágico. statdebe verificar para ver si su stdin está conectado a un archivo e informar los atributos del archivo? Presumiblemente no avanza el puntero en stdin, por lo tanto, permite fileoler el contenido de stdin
iruvar
1
@ 1_CR, sí. stat -dice statque hacer una fstat()en su fd 0.
Stéphane Chazelas
44
La { $COMMAND_A -; $COMMAND_B; } < filenamevariante no funcionará en el caso general si $ COMMAND_A realmente lee alguna entrada; Por ejemplo { cat -; cat -; } < filename, imprimirá el contenido del nombre de archivo solo una vez.
Max Nanasy
2
stat -se maneja especialmente desde coreutils-8.0 (octubre de 2009), en versiones anteriores obtendrá un error (a menos que haya llamado un archivo -de un experimento anterior, en cuyo caso obtendrá los resultados incorrectos ...)
Sr. .spuratic
1
@ mr.spuratic. Sí, y las estadísticas BSD, IRIX y zsh tampoco lo reconocerán. Con zsh e IRIX stat, puede usar stat -f0en su lugar.
Stéphane Chazelas
9

Todas estas respuestas me parecen scripts, en mayor o menor medida. Para una solución que realmente no implique secuencias de comandos y suponga que está sentado en el teclado escribiendo en un shell, puede intentar:

Enter
archivo stat somefile Esc_   Enter

Al menos en bash, zsh y ksh, en los modos vi y emacs, al presionar Escape y luego subrayar se inserta la última palabra de la línea anterior en el búfer de edición para la línea actual.

O puede usar la sustitución del historial:

Enter
archivo stat somefile ! $Enter

En bash, zsh, csh, tcsh y la mayoría de los shells, !$se reemplaza con la última palabra de la línea de comando anterior cuando se analiza la línea de comando actual.

Godlygeek
fuente
¿Qué otras opciones están disponibles en zsh / bash Esc _? ¿Pueden enlazar con la documentación oficial?
Abhijeet Rastogi
1
zsh: linux.die.net/man/1/zshzle y busque "insert-last-word"
godlygeek
1
@shadyabhi ^ Enlaces para las combinaciones de teclas estándar de bash y zsh. Tenga en cuenta que bash solo está usando readline, por lo que (la mayoría de) los bash funcionarán en cualquier aplicación que use la biblioteca readline.
godlygeek
3

La forma en que he encontrado para hacer esto razonablemente simplemente es usando xargs, toma un archivo / tubería y convierte su contenido en argumentos de programa. Esto se puede combinar con tee que divide una secuencia y la envía a dos o más programas. En su caso, necesita:

echo filename | tee >(xargs stat) >(xargs file) | cat

A diferencia de muchas de las otras respuestas, esto funcionará en bash y en la mayoría de los shells en Linux. Sugeriré que este es un buen caso de uso de una variable, pero si no puede usar una, esta es la mejor manera de hacerlo solo con tuberías y utilidades simples (suponiendo que no tenga un shell particularmente elegante).

Además, puede hacer esto para cualquier número de programas simplemente agregándolos de la misma manera que estos dos después del tee.

EDITAR

Como se sugiere en los comentarios, hay un par de fallas en esta respuesta, la primera es el potencial para el entrelazado de salida. Esto se puede solucionar así:

echo filename | tee >(xargs stat) >(( wait $!; xargs file )) | cat

Esto obligará a los comandos a ejecutarse a su vez y la salida nunca se entrelazará.

El segundo problema es evitar la sustitución del proceso que no está disponible en algunos shells, esto se puede lograr usando tpipe en lugar de tee como sigue:

echo filename | tpipe 'xargs stat' | ( wait $!; xargs file )

Esto debería ser portátil para casi cualquier shell (espero) y resuelve los problemas en el otro comentario, sin embargo, estoy escribiendo este último de memoria ya que mi sistema actual no tiene tpipe disponible.

La realidad
fuente
1
La sustitución de procesos es una característica de ksh que solo funciona en ksh(no las variantes de dominio público) bashy zsh. Tenga en cuenta que staty filese ejecutará simultáneamente, por lo que posiblemente su salida se entrelazará (¿supongo que está utilizando | catpara forzar el almacenamiento en búfer de salida para limitar ese efecto?).
Stéphane Chazelas
@ StéphaneChazelas Tienes razón sobre cat, cuando lo uso no he logrado producir un caso de entrada entrelazada cuando lo uso. Sin embargo, voy a intentar algo para producir una solución más portátil momentáneamente.
Vality
3

Esta respuesta es similar a algunas de las otras:

nombre de   archivo stat && file! #: 1

A diferencia de las otras respuestas, que repiten automáticamente el último argumento del comando anterior, este requiere que cuentes:

ls -ld nombre de   archivo && file! #: 2

A diferencia de algunas de las otras respuestas, esto no requiere comillas:

stat "useless cat"  &&  file !#:1

o

echo Sometimes a '$cigar' is just a !#:3.
Scott
fuente
2

Esto es similar a algunas de las otras respuestas, pero es más portátil que esta y mucho más simple que esta :

sh -c 'stat "$ 0"; archivo "$ 0"' nombre de archivo

o

sh -c 'stat "$ 1"; archivo "$ 1" 'sh nombrearchivo

en el que shestá asignado $0. Aunque son tres caracteres más para escribir, esta (segunda) forma es preferible porque ese $0es el nombre que le da a esa secuencia de comandos en línea. Se utiliza, por ejemplo, en los mensajes de error por la cáscara de ese guión, como cuando fileo statno se puede encontrar o no puede ser ejecutado por cualquier motivo.


Si quieres hacer

stat nombre de  archivo 1 nombre de archivo 2  nombre de archivo 3 …; archivo nombre de  archivo 1 nombre de archivo 2  nombre de archivo 3

para un número arbitrario de archivos, hacer

sh -c 'stat "$ @"; archivo "$ @" 'sh nombre de  archivo  1 nombre de archivo 2 nombre de archivo 3 ...

que funciona porque "$@"es equivalente a "$1" "$2" "$3" ….

Scott
fuente
Ese $0es el nombre que desea darle a ese script en línea. Se usa, por ejemplo, en mensajes de error del shell para ese script. Como cuando fileo statno se puede encontrar o no puede ser ejecutado por cualquier motivo. Entonces, quieres algo significativo aquí. Si no está inspirado, solo úselo sh.
Stéphane Chazelas
1

Creo que está haciendo una pregunta incorrecta, al menos por lo que está tratando de hacer. staty los filecomandos toman parámetros que son el nombre de un archivo y no el contenido del mismo. Es más tarde en el comando que hace la lectura del archivo identificado por el nombre que está especificando.

Se supone que una tubería tiene dos extremos, uno utilizado como entrada y otro como salida, y esto tiene sentido, ya que es posible que deba realizar un procesamiento diferente en el camino con diferentes herramientas hasta obtener el resultado necesario. Decir que sabe cómo canalizar salidas pero no sabe cómo canalizar entradas no es correcto en principio.

En su caso, no necesita una tubería, ya que debe proporcionar la entrada en forma de nombre de archivo. E incluso si esas herramientas ( staty file) leyeran stdin, la tubería no es relevante aquí nuevamente ya que ninguna de las herramientas debería alterar la entrada cuando llegue a la segunda.

vadimbog
fuente
1

Esto debería funcionar para todos los nombres de archivo en el directorio actual.

sh -c "$(printf 'stat -- "$1";file -- "$1";shift%.0b\n' *)" -- *
mikeserv
fuente
1
Suponiendo que los nombres de los archivos no contienen comillas dobles, barras diagonales invertidas, dólares, comillas invertidas, }... caracteres y no comience con -. Algunas printfimplementaciones (zsh, bash, ksh93) tienen a %q. Con zsh, eso podría ser:printf 'stat %q; file %1$q\n' ./* | zsh
Stéphane Chazelas
@StephaneChezales: todo eso se ha solucionado ahora, excepto la posibilidad de la cita dura. Supongo que podría manejarlo con una sola sed s///, pero se trata de alcance, y si las personas lo ponen en sus nombres de archivo, deberían usar Windows.
mikeserv
@ StéphaneChazelas - no importa - Olvidé lo fácil que eran manejar eso.
mikeserv
Estrictamente hablando, esta no es una respuesta a la pregunta que se hizo: "¿Cómo puede ejecutar ambos comandos en la misma entrada ( sin escribir la entrada ... dos veces )?" (Énfasis agregado). Su respuesta se aplica a todos los archivos en el directorio actual, e incluye *dos veces . También podrías decirlo stat -- *; file -- *.
Scott
@Scott - la entrada no se escribe dos veces - *se expande. La expansión de *más los dos comandos para cada argumento en * es la entrada. ¿Ver? printf commands\ %s\;shift *
mikeserv
1

Aquí hay algunas referencias diferentes a 'entrada', por lo que daré algunos escenarios teniendo en cuenta primero. Para su respuesta rápida a la pregunta en forma más corta :

stat testfile < <($1)> outputfile

Lo anterior realizará una estadística en el archivo de prueba, tomará (redirigirá) su STDOUT e incluirá eso en la siguiente función especial (la parte <()) y luego generará los resultados finales de lo que sea, en un nuevo archivo (archivo de salida). Se llama al archivo, luego se hace referencia a él con bash incorporado ($ 1 cada vez, hasta que comience un nuevo conjunto de instrucciones).

Su pregunta es excelente, y hay varias respuestas y formas de hacerlo, pero de hecho cambia con lo que está haciendo específicamente.

Por ejemplo, también puedes repetir eso, lo cual es bastante útil. Un uso común de esto es, en la mentalidad de código psuedo, es:

run program < <($output_from_program)> my_own.log

Tomar eso y expandir ese conocimiento le permite crear cosas como:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

Esto realizará un simple ls -A en su directorio actual, luego le dirá al tiempo que recorra cada resultado desde el ls -A a (¡y aquí es difícil!) Grep "esa palabra" en cada uno de esos resultados, y solo realice el anterior printf (en rojo) si realmente encontró un archivo con "esa palabra" en él. También registrará los resultados del grep en un nuevo archivo de texto, files_that_matched_thatword.

Salida de ejemplo:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

index.html

Todo eso simplemente imprimió el resultado ls -A, nada especial. Agregue algo para que grep esta vez:

echo "thatword" >> newfile

Ahora vuelva a ejecutarlo:

ls -A; (while read line; do printf "\e[1;31mFound a file\e[0m: $line\n"; done) < <(/bin/grep thatword * | /usr/bin/tee -a files_that_matched_thatword)

files_that_matched_thatword  index.html  newfile

Found a file: newfile:thatword

Aunque tal vez sea una respuesta más agotadora de la que está buscando en la actualidad, creo que mantener notas útiles como esta le beneficiará mucho más en futuros esfuerzos.

pulsación de tecla
fuente