xargs con redirección stdin / stdout

21

Me gustaría correr:

./a.out < x.dat > x.ans

para cada uno * .dat archivo en el directorio A .

Claro, podría hacerse por bash / python / cualquier script, pero me gusta escribir sexy one-liner. Todo lo que pude alcanzar es (aún sin ningún stdout):

ls A/*.dat | xargs -I file -a file ./a.out

Pero -a en xargs no entiende reemplazar-str 'archivo'.

Gracias por ayudar.

Nikolay Vyahhi
fuente
Además de las otras buenas respuestas aquí, puede usar la opción -a de GNU xargs.
James Youngman

Respuestas:

30

En primer lugar, no use la lssalida como una lista de archivos . Usar expansión de concha o find. Vea a continuación las posibles consecuencias del mal uso de ls + xargs y un ejemplo de xargsuso adecuado .

1. Forma simple: para bucle

Si desea procesar solo los archivos A/, entonces un simple forbucle debería ser suficiente:

for file in A/*.dat; do ./a.out < "$file" > "${file%.dat}.ans"; done

2. pre1 ¿ Por qué no   ls | xargs ?

He aquí un ejemplo de lo mal que puede resultar si se utiliza lscon xargspara el trabajo. Considere un siguiente escenario:

  • primero, creemos algunos archivos vacíos:

    $ touch A/mypreciousfile.dat\ with\ junk\ at\ the\ end.dat
    $ touch A/mypreciousfile.dat
    $ touch A/mypreciousfile.dat.ans
    
  • ver los archivos y que no contienen nada:

    $ ls -1 A/
    mypreciousfile.dat
    mypreciousfile.dat with junk at the end.dat
    mypreciousfile.dat.ans
    
    $ cat A/*
    
  • ejecuta un comando mágico usando xargs:

    $ ls A/*.dat | xargs -I file sh -c "echo TRICKED > file.ans"
    
  • el resultado:

    $ cat A/mypreciousfile.dat
    TRICKED with junk at the end.dat.ans
    
    $ cat A/mypreciousfile.dat.ans
    TRICKED
    

Así que has logrado sobrescribir ambas mypreciousfile.daty mypreciousfile.dat.ans. Si hubiera algún contenido en esos archivos, se habría borrado.


2. Utilizando   xargs : la forma correcta con  find 

Si desea insistir en usar xargs, use -0(nombres con terminación nula):

find A/ -name "*.dat" -type f -print0 | xargs -0 -I file sh -c './a.out < "file" > "file.ans"'

Note dos cosas:

  1. de esta manera creará archivos con .dat.ansfinalización;
  2. esto se romperá si algún nombre de archivo contiene un signo de comillas ( ").

Ambos problemas pueden resolverse mediante diferentes formas de invocación de shell:

find A/ -name "*.dat" -type f -print0 | xargs -0 -L 1 bash -c './a.out < "$0" > "${0%dat}ans"'

3. Todo hecho dentro find ... -exec

 find A/ -name "*.dat" -type f -exec sh -c './a.out < "{}" > "{}.ans"' \;

Esto, nuevamente, produce .dat.ansarchivos y se romperá si contienen nombres de archivos ". Para bashhacerlo , use y cambie la forma en que se invoca:

 find A/ -name "*.dat" -type f -exec bash -c './a.out < "$0" > "${0%dat}ans"' {} \;
rozcietrzewiacz
fuente
+1 por mencionar que no se debe analizar la salida de ls.
rahmu
2
La opción 2 se interrumpe cuando los nombres de archivo contienen ".
thiton
2
Muy buen punto, gracias! Actualizaré en consecuencia.
rozcietrzewiacz
Solo quiero mencionar que si zshse usa como shell (y SH_WORD_SPLIT no está configurado), no es necesario tener en cuenta todos los casos especiales desagradables (espacios en blanco ", en el nombre del archivo, etc.). Lo trivial for file in A/*.dat; do ./a.out < $file > ${file%.dat}.ans ; donefunciona en todos los casos.
jofel
-1 porque estoy tratando de descubrir cómo hacer xargs con stdin y mi pregunta no tiene nada que ver con archivos o find.
Mehrdad
2

Intente hacer algo como esto (la sintaxis puede variar un poco dependiendo del shell que use):

$ for i in $(find A/ -name \*.dat); do ./a.out < ${i} > ${i%.dat}.ans; done

rahmu
fuente
Eso no funcionaría. Intentaría operar en cosas como somefile.dat.daty redirigir toda la salida a un solo archivo.
rozcietrzewiacz
Tienes razón. Edité la solución para corregirlo.
rahmu
OK - Casi bien :) Solo somefile.dat.anslas cosas de salida no se verían tan bien.
rozcietrzewiacz
1
Editado! No sabía sobre '%'. Funciona de maravilla, gracias por la propina.
rahmu
1
Agregar un -type filesería bueno (no se puede < directory), y esto entristece al hada inusual del nombre de archivo.
Mat
2

Para patrones simples, el bucle for es apropiado:

for file in A/*.dat; do
    ./a.out < "${file}" > "${file%.dat}.ans" # Never forget the QUOTES!
done

Para casos más complejos en los que necesita otra utilidad para enumerar los archivos (zsh o bash 4 tienen patrones lo suficientemente potentes que rara vez necesita encontrar, pero si desea permanecer dentro del shell POSIX o usar un shell rápido como el guión, necesitará encontrar cualquier cosa no trivial), mientras que leer es más apropiado:

find A -name '*.dat' -print | while IFS= read -r file; do
   ./a.out < "${file}" > "${file%.dat}.ans" # Never forget the QUOTES!
done

Esto manejará espacios, porque la lectura está (por defecto) orientada a líneas. No manejará las nuevas líneas ni las barras invertidas, porque de forma predeterminada interpreta las secuencias de escape (que en realidad le permiten pasar una nueva línea, pero find no puede generar ese formato). Muchos shells tienen la -0opción de leer, por lo que en esos puedes manejar todos los caracteres, pero desafortunadamente no es POSIX.

Jan Hudec
fuente
2

Use GNU Paralelo:

parallel ./a.out "<{} >{.}.ans" ::: A/*.dat

Bonificación adicional: el procesamiento se realiza en paralelo.

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

Ole Tange
fuente
1

Creo que necesita al menos una invocación de shell en los xargs:

ls A/*.dat | xargs -I file sh -c "./a.out < file > file.ans"

Editar: debe tenerse en cuenta que este enfoque no funciona cuando los nombres de los archivos contienen espacios en blanco. No puedo trabajar Incluso si usó find -0 y xargs -0 para hacer que xargs entienda los espacios correctamente, la llamada de shell -c los graznaría. Sin embargo, el OP solicitó explícitamente una solución de xargs, y esta es la mejor solución de xargs que se me ocurrió. Si el espacio en blanco en los nombres de archivo puede ser un problema, use find -exec o un bucle de shell.

thiton
fuente
Eche un vistazo a por qué es una mala idea analizar la lssalida .
rozcietrzewiacz
@rozcietrzewiacz: En general, claro, pero supongo que alguien que intenta hacer xargs vudú lo sabe. Dado que estos nombres de archivo experimentan otra expansión de shell, de todos modos tienen que comportarse bien.
thiton
1
Estás equivocado acerca de la expansión del shell. lsgenera cosas como espacios sin escapar y ese es el problema.
rozcietrzewiacz
@rozcietrzewiacz: Sí, entiendo ese problema. Ahora solo suponga que escaparía adecuadamente de estos espacios y los colocaría en xargs: se reemplazan en la cadena -c de sh, sh tokeniza la cadena -c y todo se rompe.
thiton
Sí. Ya ves, el análisis lsno es bueno. Pero se xargs puede usar de forma segura con find: consulte la sugerencia n. ° 2 en mi respuesta.
rozcietrzewiacz
1

No hay necesidad de complicarlo. Podrías hacerlo con un forbucle:

for file in A/*.dat; do
  ./a.out < "$file" >"${file%.dat}.ans"
done

el ${file%.dat}.ansbit eliminará el .datsufijo del nombre de archivo del nombre de archivo $filey en su lugar se agregará .ansal final.

Kusalananda
fuente