En esos casos sorprendentemente frecuentes donde lo que realmente necesita hacer es procesar todas las líneas no vacías dentro de una variable de alguna manera (incluido contarlas), puede configurar IFS en solo una nueva línea y luego usar el mecanismo de división de palabras del shell para romper Las líneas no vacías separadas.
Por ejemplo, aquí hay una pequeña función de shell que totaliza las líneas no vacías dentro de todos los argumentos proporcionados:
lines() (
IFS='
'
set -f #disable pathname expansion
set -- $*
echo $#
)
Aquí se usan paréntesis, en lugar de llaves, para formar el comando compuesto para el cuerpo de la función. Esto hace que la función se ejecute en un subshell para que no contamine la configuración de expansión de nombre de ruta y variable IFS del mundo exterior en cada llamada.
Si desea iterar sobre líneas no vacías, puede hacerlo de manera similar:
IFS='
'
set -f
for line in $lines
do
printf '[%s]\n' $line
done
Manipular IFS de esta manera es una técnica que a menudo se pasa por alto, también útil para hacer cosas como analizar nombres de ruta que podrían contener espacios desde entradas en columnas delimitadas por tabuladores. Sin embargo, debe ser consciente de que eliminar deliberadamente el carácter de espacio generalmente incluido en la configuración predeterminada de IFS de espacio-tabulación-nueva línea puede terminar deshabilitando la división de palabras en lugares donde normalmente esperaría verlo.
Por ejemplo, si está utilizando variables para construir una línea de comando complicada para algo así ffmpeg
, es posible que desee incluir -vf scale=$scale
solo cuando la variable scale
se establece en algo que no está vacío. Normalmente, podría lograr esto con, ${scale:+-vf scale=$scale}
pero si IFS no incluye su carácter de espacio habitual en el momento en que se realiza esta expansión de parámetros, el espacio entre -vf
y scale=
no se utilizará como un separador de palabras y se ffmpeg
pasará todo -vf scale=$scale
como un argumento único, que no entenderá
Para solucionar esto, puede que sea necesario para asegurarse de que IFS se fijó de manera más normal antes de hacer la ${scale}
expansión, o hacer dos expansiones: ${scale:+-vf} ${scale:+scale=$scale}
. La división de la palabra que hace el shell en el proceso de análisis inicial de las líneas de comando, a diferencia de la división que realiza durante la fase de expansión del procesamiento de esas líneas de comando, no depende de IFS.
Otra cosa que podría valer la pena si va a hacer este tipo de cosas sería crear dos variables de shell globales para contener solo una pestaña y una nueva línea:
t=' '
n='
'
De esa manera, puede incluir $t
y $n
en expansiones donde necesita pestañas y nuevas líneas, en lugar de ensuciar todo su código con espacios en blanco entre comillas. Si prefiere evitar los espacios en blanco citados por completo en un shell POSIX que no tiene otro mecanismo para hacerlo, printf
puede ayudar, aunque necesita un poco de violín para evitar la eliminación de nuevas líneas en las expansiones de comandos:
nt=$(printf '\n\t')
n=${nt%?}
t=${nt#?}
A veces, configurar IFS como si fuera una variable de entorno por comando funciona bien. Por ejemplo, aquí hay un bucle que lee un nombre de ruta que puede contener espacios y un factor de escala de cada línea de un archivo de entrada delimitado por tabulaciones:
while IFS=$t read -r path scale
do
ffmpeg -i "$path" ${scale:+-vf scale=$scale} "${path%.*}.out.mkv"
done <recode-queue.txt
En este caso, la read
versión integrada ve el IFS establecido en solo una pestaña, por lo que no dividirá la línea de entrada que lee en los espacios también. Pero IFS=$t set -- $lines
no funciona: el shell se expande a $lines
medida que construye los set
argumentos incorporados antes de ejecutar el comando, por lo que la configuración temporal de IFS de una manera que se aplica solo durante la ejecución del propio desarrollo llega demasiado tarde. Esta es la razón por la cual los fragmentos de código que he dado sobre todo establecen IFS en un paso separado, y por qué tienen que lidiar con el problema de preservarlo.
wc -l
es exactamente equivalente al original:<<<$foo
agrega una nueva línea al valor de$foo
(incluso si$foo
estaba vacío). Explico en mi respuesta por qué esto puede no haber sido lo que se quería, pero es lo que se preguntó.