¿Cómo hago un bucle sobre las líneas en STDIN y ejecuto un comando de shell?

¿Cómo hago un bucle sobre las líneas en STDIN y ejecuto un comando de shell?

Me gustaría ejecutar un comando de shell en cada línea tomada de STDIN.

En este caso, me gustaría ejecutar xargs mv. Por ejemplo, dadas dos líneas:

mfoo foo
mbar bar

Me gustaría ejecutar:

xargs mv mfoo foo
xargs mv mbar bar

Probé las siguientes estrategias con ruby, awky xargs. Sin embargo, lo estoy haciendo mal:

Justo xargs:

$ echo "mbar bar\nmbaz baz" | xargs mv
usage: mv [-f | -i | -n] [-v] source target
       mv [-f | -i | -n] [-v] source ... directory

A través de awk:

$ echo "mbar bar\nmbaz baz" | awk '{ system("xargs $0") }'

A través de ruby:

$ echo "mbar bar\nmbaz baz" | ruby -ne '`xargs mv`'
$ ls
cat  foo  mbar mbaz

Tengo algunas preguntas:

  • ¿Cómo hago lo que estoy intentando hacer?
  • ¿Qué hay de malo en cada uno de mis intentos?
  • ¿Existe una mejor manera de "pensar" en lo que estoy intentando hacer?

Estoy especialmente confundido porque mi xargsintento no funciona porque lo siguiente funciona:

$ echo "foo\nbar" | xargs touch
$ ls
bar foo

Respuesta1

echo "mbar bar\nmbaz baz" | xargs mv

Con xargsdeberías usar la -topción para ver qué está pasando. Entonces, en el caso anterior, si invocamos xargscon -t, ¿qué vemos?

mv mbar bar mbaz baz

Entonces obviamente no es correcto. Lo que sucedió fue que, xargscomo un cocodrilo hambriento, se comió todos los args que le alimentaban a través de la tubería echo. Entonces necesitas una manera de limitar la liberación de argumentos al cocodrilo. Y dado que solicitó por línea, lo que necesita es la opción -lo -Lpara POSIX.

echo "mbar bar\nmbaz baz" | xargs -l -t mv

Manera POSIX:

echo "mbar bar\nmbaz baz" | xargs -L 1 -t mv

mv mbar bar
mv mbaz baz

Y esto es lo que querías. HT

Respuesta2

Puedes ir de esta manera:

echo -e "foo bar\ndoo dar" | xargs -n 2 mv

Para leer desde el archivo:

cat lines.txt | xargs -n 2 mv

o

xargs -a lines.txt -n 2 mv

Respuesta3

xargsPuede que no se comporte como espera. Vea el comentario de @MichaelHomer a esta pregunta:

xargs mv mfoo fooesperará la entrada y ejecutará mv mfoo foo $x_1 $x_2 $x_3...para cada línea $x_nde entrada que obtenga. @MichaelHomer

Esto se puede confirmar leyendo elxargspágina de manual:

DESCRIPCIÓN
La utilidad xargs lee cadenas delimitadas por espacios, tabulaciones, nuevas líneas y finales de archivo de la entrada estándar y ejecuta la utilidad con las cadenas como argumentos.

En la práctica, parece que xargsse ignorarán las nuevas líneas cuando se introduzcan argumentos en la utilidad de línea de comando proporcionada como primer argumento. Por ejemplo, observe lo que sucede con la nueva línea:

$ echo "foo bar\ncat dog"
foo bar
cat dog
$ echo "foo bar\ncat dog" | xargs echo
foo bar cat dog

Este comportamiento se puede controlar con el-nbandera:

-n numero

Establezca el número máximo de argumentos tomados de la entrada estándar para
cada invocación de utilidad.

Volviendo al ejemplo anterior, si queremos xargstomar solo 2 argumentos y luego salir, podemos usarEl enfoque de @ddnomad:

$ echo "foo bar\ncat dog" | xargs -n 2 echo
foo bar
cat dog

Además, como se muestra en los comentarios de la respuesta de Rakesh Sharma, en BSD xargspuede especificar el número de líneas que se leerán con la -Lbandera. Desde la página de manual:

-Número L

Llame a la utilidad para cada línea numérica leída. Si se alcanza el EOF y
se han leído menos líneas que el número, se llamará a la utilidad con las líneas disponibles.

Igualmente útil para las pruebas es el -tinterruptor:

Revisando nuestro ejemplo, ambos de los siguientes funcionarán; sin embargo, el segundo ejemplo es mejor porque es lo que se pretendía en este caso:

$ echo "foo bar\ncat dog" | xargs -n 2 echo
foo bar
cat dog
$ echo "foo bar\ncat dog" | xargs -L 1 echo
foo bar
cat dog

Nota de la página de manual:

Las opciones -L y -n son mutuamente excluyentes; Se utilizará el último proporcionado.

Respuesta4

Existe una solución muy sencilla que no utiliza xargs.
Defina esta función:

$ mv2() { while (($# >1 )); do echo mv "$1" "$2"; shift 2; done; }

Luego, simplemente llámalo con la lista de nombres de archivos necesarios (no se requieren nuevas líneas):

$ mv2 mbar bar mbaz baz
mv mbar bar
mv mbaz baz

Elimina el eco para que la función realmente funcione.

información relacionada