¿Ejecutar un comando una vez por línea de entrada canalizada?

162

Quiero ejecutar un comando java una vez por cada partido de ls | grep pattern -. En este caso, creo que podría hacerlo, find pattern -exec java MyProg '{}' \;pero tengo curiosidad sobre el caso general: ¿hay una manera fácil de decir "ejecutar un comando una vez por cada línea de entrada estándar"? (En pescado o golpe)

Xodarap
fuente

Respuestas:

92

Eso es lo que xargshace.

... | xargs command
Keith
fuente
25
No exactamente. printf "foo bar\nbaz bat" | xargs echo wheecederá whee foo bar baz bat. ¿Quizás agregar las opciones -Lo -n?
Jander
3
@Jander La pregunta era bastante general, así que le di la herramienta general. Es cierto que tendrá que ajustar su comportamiento con opciones según las circunstancias específicas.
Keith
44
... | tr '\ n' '\ 0' | xargs -0
vrdhn
77
como "las circunstancias específicas que dan la respuesta correcta a la pregunta". :)
mattdm
77
Si desea ver la forma correcta de hacer esto con xargs, vea mi respuesta a continuación.
Michael Goldshteyn
167

La respuesta aceptada tiene la idea correcta, pero la clave es pasar xargsel -n1interruptor, lo que significa "Ejecutar el comando una vez por línea de salida:"

cat file... | xargs -n1 command

O, para un solo archivo de entrada, puede evitar la tubería por catcompleto y simplemente ir con:

<file xargs -n1 command
Michael Goldshteyn
fuente
1
También de interés es la posibilidad de xargsque no se ejecutará si stdinestá vacía: --no-run-if-empty -r: si la entrada estándar no contiene ningún Sin espacios, no se ejecuta el comando. Normalmente, el comando se ejecuta una vez incluso si no hay entrada. Esta opción es una extensión GNU.
Ronan Jouchet
44
¿Cómo acceder a la línea interior command?
BT
Este es el uso correcto de xargs. Sin -n1, solo funciona en comandos que tratan las listas de parámetros como invocaciones múltiples, lo que no todos hacen.
masterxilo
3
printf "foo bar \ nbaz bat" | xargs -n1 echo donde se divide por palabras y no por líneas
Gismo Ranas
112

En Bash o cualquier otro shell de estilo Bourne (ash, ksh, zsh, ...):

while read -r line; do command "$line"; done

read -rlee una sola línea desde la entrada estándar ( readsin -rinterpretar barras invertidas, no quiere eso). Por lo tanto, puede hacer lo siguiente:

$ command | while read -r line; do command "$line"; done  

$ while read -r line; do command "$line"; done <file
Steven D
fuente
66
Cuando lo intenté tail -f syslog | grep -e something -e somethingelse| while read line; do echo $line; doneno funcionó. Funcionó con un archivo entubado en el whilebucle, funcionó solo con tail -f, funcionó con solo grep, pero no con ambas tuberías. Dando a la grepde la --line-bufferedopción que hizo el trabajo
Esto funciona también cuando cada línea debe enviarse a stdin:command | while read -r line; do echo "$line" | command ; done
Den
21

Estoy de acuerdo con Keith, xargs es la herramienta más general para el trabajo.

Usualmente uso un enfoque de 3 pasos.

  • hacer las cosas básicas hasta que tenga algo con lo que le gustaría trabajar
  • prepare la línea con awk para que obtenga la sintaxis correcta
  • luego deja que xargs lo ejecute, tal vez con la ayuda de bash.

Hay formas más pequeñas y rápidas, pero estas formas casi siempre funcionan.

Un simple ejemplo:

ls | 
grep xls | 
awk '{print "MyJavaProg --arg1 42 --arg2 "$1"\0"}' | 
xargs -0 bash -c

las 2 primeras líneas seleccionan algunos archivos para trabajar, luego awk prepara una buena cadena con un comando para ejecutar y algunos argumentos y $ 1 es la entrada de la primera columna de la tubería. Y finalmente me aseguro de que xargs envíe esta cadena a bash que solo la ejecute.

Es un poco exagerado, pero esta receta me ha ayudado en muchos lugares ya que es muy flexible.

Johan
fuente
66
Tenga en cuenta que xargs -0utiliza el byte nulo como separador de registros, por lo que su declaración de impresión awk debería serprintf("MyJavaProg --args \"%s\"\0",$1)
Glenn Jackman
@glenn: Perdí el carácter nulo, actualizará la respuesta
Johan
@Johan no es un gran problema, pero si lo estás usando awk, puedes hacer que coincida con el patrón y omitir grep , por ejemplo,ls | awk '/xls/ {print...
Eric Renouf
15

GNU Parallel está hecho para ese tipo de tareas. El uso más simple es:

cat stuff | grep pattern | parallel java MyProg

Mire el video de introducción para obtener más información: http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
fuente
1
No hay una necesidad real del cataquí, ya que greppuede leer directamente el archivo
Eric Renouf
1
Gracias por el enlace, no estoy necesariamente de acuerdo en que sea más fácil de leer, pero es bueno saber que se consideró independientemente. Solo ahora dudaría un poco de que el enlace en realidad no se aplica aquí, ya que la alternativa no es realmente, < stuff grep patternpero grep pattern stuffno requiere redirección o gato. Aún así, eso no cambia materialmente su argumento y si cree que es más claro usar siempre las cosas en una tubería que comienza cat, entonces el poder para usted
Eric Renouf
8

Además, while readrealice un bucle en la concha de pescado (supongo que quiere la concha de pescado, teniendo en cuenta que usó la etiqueta de ).

command | while read line
    command $line
end

Pocos puntos a tener en cuenta.

  • readno toma -rargumentos, y no interpreta las barras diagonales inversas, para facilitar el uso más común de los casos de uso.
  • No necesita citar $line, ya que a diferencia de bash, fish no separa las variables por espacios.
  • commanden sí mismo es un error de sintaxis (para detectar el uso de argumentos de marcador de posición). Reemplácelo con el comando real.
Konrad Borowski
fuente
¿No whilenecesita ser emparejado con do& en donelugar de end?
aff
@aff Esto se trata específicamente de la concha de pescado, que tiene una sintaxis diferente.
Konrad Borowski el
Ah, eso es lo que significa el pez.
aff
6

Si necesita controlar dónde se inserta exactamente el argumento de entrada en su línea de comando o si necesita repetirlo varias veces, debe usarlo xargs -I{}.

EJEMPLO 1

Cree una estructura de carpetas vacía another_folderque refleje las subcarpetas en el directorio actual:

    ls -1d ./*/ | xargs -I{} mkdir another_folder/{}
EJEMPLO # 2

Aplique una operación en una lista de archivos proveniente de stdin, en este caso haga una copia de cada .htmlarchivo agregando una .bakextensión:

    find . -iname "*.html" | xargs -I{} cp {} {}.bak

Desde la xargspágina del manual para MacOS / BSD :

 -I replstr
         Execute utility for each input line, replacing one or more occurrences of
         replstr in up to replacements (or 5 if no -R flag is specified) arguments
         to utility with the entire line of input.  The resulting arguments, after
         replacement is done, will not be allowed to grow beyond 255 bytes; this is
         implemented by concatenating as much of the argument containing replstr as
         possible, to the constructed arguments to utility, up to 255 bytes.  The
         255 byte limit does not apply to arguments to utility which do not contain
         replstr, and furthermore, no replacement will be done on utility itself.
         Implies -x.

xargsPágina del manual de Linux :

   -I replace-str
          Replace  occurrences of replace-str in the initial-
          arguments with names read from standard input.  Al
          so,  unquoted  blanks do not terminate input items;
          instead the separator  is  the  newline  character.
          Implies -x and -L 1.
ccpizza
fuente
1

Cuando se trata de entradas potencialmente no higienizadas, me gusta ver todo el trabajo 'enunciado' línea por línea para una inspección visual antes de ejecutarlo (especialmente cuando es algo destructivo como limpiar los buzones de las personas).

Entonces, lo que hago es generar una lista de parámetros (es decir, nombres de usuario), alimentarlo a un archivo en un registro por línea, de esta manera:

johndoe  
jamessmith  
janebrown  

Luego abro la lista vimy la destruyo con expresiones de búsqueda y reemplazo hasta que obtengo una lista de comandos completos que deben ejecutarse, así:

/bin/rm -fr /home/johndoe  
/bin/rm -fr /home/jamessmith 

De esta manera, si su expresión regular está incompleta, verá en qué comando tendrá problemas potenciales (es decir /bin/rm -fr johnnyo connor). De esta manera, puede deshacer su expresión regular y volver a intentarlo con una versión más confiable. El cambio de nombre es conocido por esto, porque es difícil ocuparse de todos los casos extremos como Van Gogh, O'Connors, St. Clair, Smith-Wesson.

Tener set hlsearches útil para hacer esto vim, ya que resaltará todas las coincidencias, por lo que puede detectar fácilmente si no coincide o coincide de manera no intencionada.

Una vez que su expresión regular es perfecta y detecta todos los casos en los que puede probar / pensar, generalmente la convierto en una expresión sed para que pueda automatizarse completamente para otra ejecución.

Para los casos en que el número de líneas de entrada le impide realizar una inspección visual, le recomiendo hacer eco del comando en la pantalla (o mejor aún, un registro) antes de que se ejecute, por lo que si se produce un error, sabrá exactamente qué comando causó que falle Luego puede volver a su expresión regular original y ajustar una vez más.

Marcin
fuente
0

Si un programa ignora la canalización pero acepta archivos como argumentos, puede simplemente señalarlo al archivo especial /dev/stdin.

No estoy familiarizado con Java, pero aquí hay un ejemplo de cómo lo haría para bash:

$ echo $'pwd \n cd / \n pwd' |bash /dev/stdin
/home/rolf
/

El $ es necesario para que bash se traduzca \nen nuevas líneas. No estoy seguro de por qué.

Rolf
fuente
0

Aquí, una copia que puedes usar de inmediato:

cat list.txt | xargs -I{} command parameter {} parameter

El elemento de la lista se colocará donde está el {} y el resto del comando y los parámetros se usarán tal cual.

DustWolf
fuente