Sé que puedo resolver este problema de varias maneras, pero me pregunto si hay una manera de hacerlo usando solo las funciones integradas de bash, y si no, cuál es la forma más eficiente de hacerlo.
Tengo un archivo con contenido como
AAA
B C DDD
FOO BAR
con lo cual solo quiero decir que tiene varias líneas y cada línea puede o no tener espacios. Quiero ejecutar un comando como
cmd AAA "B C DDD" "FOO BAR"
Si uso cmd $(< file)
obtengo
cmd AAA B C DDD FOO BAR
y si uso cmd "$(< file)"
me sale
cmd "AAA B C DDD FOO BAR"
¿Cómo hago que cada línea trate exactamente un parámetro?
bash
command-substitution
Viejo pro
fuente
fuente
Respuestas:
Portablemente:
O usando una subshell para hacer que la
IFS
opción y los cambios sean locales:El shell realiza la división de campo y la generación de nombre de archivo en el resultado de una variable o sustitución de comando que no está entre comillas dobles. Por lo tanto, debe desactivar la generación de nombre de archivo
set -f
y configurar la división de campos conIFS
para que solo las líneas nuevas separen los campos.No hay mucho que ganar con las construcciones bash o ksh. Puede hacer
IFS
local a una función, pero noset -f
.En bash o ksh93, puede almacenar los campos en una matriz, si necesita pasarlos a múltiples comandos. Debe controlar la expansión en el momento en que crea la matriz. Luego se
"${a[@]}"
expande a los elementos de la matriz, uno por palabra.fuente
Puede hacer esto con una matriz temporal.
Preparar:
Llena la matriz:
Usa la matriz:
No puedo encontrar una manera de hacerlo sin esa variable temporal, a menos que el
IFS
cambio no sea importantecmd
, en cuyo caso:Deberías hacerlo.
fuente
IFS
Siempre me confunde.IFS=$'\n' cmd $(<input)
no funcionaIFS=$'\n'; cmd $(<input); unset IFS
funciona. ¿Por qué? Supongo que usaré(IFS=$'\n'; cmd $(<input))
IFS=$'\n' cmd $(<input)
no funciona porque solo se estableceIFS
en el entorno decmd
.$(<input)
se expande para formar el comando, antes de queIFS
se realice la asignación a .Parece que la forma canónica de hacer esto
bash
es algo así comoo, si su versión de bash tiene
mapfile
:La única diferencia que puedo encontrar entre el mapfile y el ciclo while-read versus el one-liner
es que el primero convertirá una línea en blanco en un argumento vacío, mientras que el de una línea ignorará una línea en blanco. En este caso, el comportamiento de una línea es lo que preferiría de todos modos, así que doble bonificación por ser compacto.
Lo usaría
IFS=$'\n' cmd $(<file)
pero no funciona, porque$(<file)
se interpreta para formar la línea de comando antes de queIFS=$'\n'
surta efecto.Aunque no funciona en mi caso, ahora he aprendido que muchas herramientas admiten líneas de terminación en
null (\000)
lugar denewline (\n)
lo que facilita mucho esto al tratar, por ejemplo, los nombres de archivos, que son fuentes comunes de estas situaciones :alimenta una lista de nombres de archivos completamente calificados como argumentos para md5 sin ningún problema o interpolación o lo que sea. Eso lleva a la solución no incorporada
aunque esto también ignora las líneas vacías, aunque captura líneas que solo tienen espacios en blanco.
fuente
cmd $(<file)
valores sin citar (usar la habilidad de bash para dividir palabras) siempre es una apuesta arriesgada. Si hay alguna línea*
, el shell la ampliará a una lista de archivos.Puede usar el bash incorporado
mapfile
para leer el archivo en una matrizo, no probado,
xargs
podría hacerlofuente
xargs
tampoco ayuda.xargs -d
oxargs -L
-d
opción yxargs -L 1
ejecuta el comando una vez por línea, pero aún divide los argumentos en espacios en blanco.mapfile
es muy útil para mí, ya que toma líneas en blanco como elementos de la matriz, lo que elIFS
método no hace.IFS
trata las nuevas líneas contiguas como un delimitador único ... Gracias por presentarlo, ya que no conocía el comando (aunque, según los datos de entrada del OP y la línea de comando esperada, parece que realmente quiere ignorar las líneas en blanco).La mejor manera que pude encontrar. Solo funciona.
fuente
IFS
espacio, pero la pregunta es no dividirse en el espacio?Archivo
El bucle más básico (portátil) para dividir un archivo en nuevas líneas es:
Que imprimirá:
Sí, con IFS predeterminado = spacetabnewline.
Por que funciona
IFS
necesario.set -f
necesario.-r
opción (sin formato) es evitar la eliminación de la mayoría de la barra invertida.Eso no funcionará si se necesita dividir y / o pegar. En tales casos se necesita una estructura más compleja.
Si necesita (aún portátil) para:
IFS= read -r line
IFS=':' read -r a b c
.Divida el archivo en algún otro carácter (no portátil, funciona con ksh, bash, zsh):
Expansión
Por supuesto, el título de su pregunta se trata de dividir la ejecución de un comando en nuevas líneas evitando la división en espacios.
La única forma de separarse del shell es dejar una expansión sin comillas:
Eso está controlado por el valor de IFS y, en las expansiones no citadas, también se aplica el globbing. Para hacer ese trabajo, necesitas:
Desmarque la opción de shell globbing
set +f
:set + f IFS = '' cmd $ (<archivo)
Por supuesto, eso cambia el valor de IFS y de globbing para el resto del script.
fuente