Bash globbing y argumento pasando

8

Tengo el siguiente script bash simplificado

#!/bin/bash

files=("$@")

if [ "X$files" = "X" ]; then
  files=$HOME/print/*.pdf;
fi

for file in "${files[@]}"; do
  ls "$file";
done

Si paso argumentos (nombres de archivo) como parámetros, este script imprimirá los nombres de archivo adecuados. Por otro lado, si no paso argumentos, imprimirá

/home/user/print/*.pdf: No such file or directory

¿Por qué los nombres de archivo no se expanden en este caso y cómo lo soluciono? Tenga en cuenta que uso las construcciones files=("$@")y "${files[@]}"porque leí que es preferible a los "archivos = $ *" habituales.

Highsciguy
fuente
¿Dónde es files=$*siempre usual ? Eso está completamente mal .
Stéphane Chazelas
Lo habitual es relativo, correcto. Me refería a un método que no usa matrices. ¿Qué harás entonces?
highsciguy

Respuestas:

10

Estás asignando filescomo una variable escalar en lugar de una variable de matriz .

En

 files=$HOME/print/*.pdf

Estás asignando una cadena similar /home/highsciguy/print/*.pdfa la $filesvariable escalar (también conocida como cadena).

Utilizar:

files=(~/print/*.pdf)

o

files=("$HOME"/print/*.pdf)

en lugar. El shell expandirá ese patrón global en una lista de rutas de archivo y asignará cada una de ellas a los elementos de la $files matriz .

La expansión del globo se realiza en el momento de la asignación.

No tiene que usar funciones sh no estándar, y puede usar las de su sistema en shlugar de bashaquí escribiéndolas:

#!/bin/sh -

[ "$#" -gt 0 ] || set -- ~/print/*.pdf

for file do
  ls -d -- "$file"
done

setes asignar la "$@"matriz de parámetros posicionales.

Otro enfoque podría haber sido almacenar el patrón globbing en una variable escalar:

files=$HOME/print/*.pdf

Y haga que el shell expanda el glob en el momento en $files que se expande la variable.

IFS= # disable word splitting
for file in $files; do ...

Aquí, debido a $filesque no se cita (lo que normalmente no se debe hacer), su expansión está sujeta a la división de palabras (que hemos desactivado aquí) y la generación de nombres de archivo / globbing.

Por lo tanto *.pdf , se expandirá a la lista de archivos coincidentes. Sin embargo, si $HOMEcontuviera caracteres comodín, también podrían expandirse, por lo que todavía es preferible usar una variable de matriz.

Stéphane Chazelas
fuente
4

Es posible que haya visto cosas como files=$*y files=~/print/*.pdfen conchas antiguas sin matrices, y luego ls $files.

Una sustitución de variable que no está entre comillas dobles interpreta el valor de la variable como una lista separada por espacios en blanco de patrones comodín de shell que se reemplazan por nombres de archivo coincidentes, si los hay. Por ejemplo, después files=~/print/*.pdf, se ls $filesexpande a algo así como lscon los argumentos /home/highsciguy/print/bar.pdf, /home/highsciguy/print/foo.pdfetc. En el caso files=$*, esta asignación concatena los argumentos pasados ​​al script con espacios en el medio y los ls $filesdivide de nuevo.

Todo esto se descompone si tiene nombres de archivo que contienen espacios en blanco o caracteres globales, por lo que no debe hacer las cosas de esta manera. Utilice matrices en su lugar.

files=("$@")
if ((${#files[@]} == 0)); then
  files=("$HOME"/print/*.pdf)
fi

Tenga en cuenta que

  • Todas las asignaciones de matriz requieren paréntesis alrededor de los valores de la matriz: var=(…).
  • Para probar si una matriz está vacía, verifique su longitud. "$files"está vacío cuando fileses una matriz cuyo elemento del índice 0 no está establecido o una cadena vacía. También [ "X$foo" = "X" ]es una forma obsoleta de probar si $fooestá vacía: todos los shells modernos se implementan [ -n "$foo" ]correctamente. En bash, puedes usar [[ -n $foo ]].

En los shells que no admiten arrays, de hecho hay un array: los parámetros posicionales para el shell o la función actual. Aquí, realmente no necesita la filesmatriz, de hecho, sería más fácil usar los parámetros posicionales.

#!/bin/sh
if [ "$#" -eq 0 ]; then
  set -- ~/print/*.pdf
fi
for file do 
Gilles 'SO- deja de ser malvado'
fuente