Bash globbing e argumento passando

7

Eu tenho o seguinte script bash simplificado

#!/bin/bash

files=("$@")

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

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

Se eu passar argumentos (nomes de arquivo) como parâmetros, esse script imprimirá os nomes de arquivos apropriados. Por outro lado, se eu não passar argumentos, ele irá imprimir

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

Por que os nomes de arquivo não são expandidos neste caso e como corrigi-lo? Note que eu uso os constructos files=("$@") e "${files[@]}" porque eu li que é preferível ao usual "files = $ *".

    
por highsciguy 18.11.2013 / 13:03

2 respostas

10

Você está atribuindo files como uma variável escalar em vez de uma variável matriz .

Em

 files=$HOME/print/*.pdf

Você está atribuindo string como /home/highsciguy/print/*.pdf à variável $files escalar (string).

Uso:

files=(~/print/*.pdf)

ou

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

em vez disso. O shell expandirá esse padrão de globbing em uma lista de caminhos de arquivos e atribuirá cada um deles a elementos da matriz $files .

A expansão do glob é feita no momento da atribuição.

Você não precisa usar recursos sh não padrão e pode usar o sh do seu sistema em vez de bash escrevendo:

#!/bin/sh -

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

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

set é atribuir a matriz "$@" de parâmetros posicionais.

Outra abordagem poderia ter sido armazenar o padrão de globbing em uma variável escalar:

files=$HOME/print/*.pdf

E faça o shell expandir o glob no momento em que a variável $files for expandida.

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

Aqui, porque $files não é citado (o que você normalmente não deve fazer), sua expansão está sujeita à divisão de palavras (que desabilitamos aqui) e geração de globbing / filename.

Assim, o *.pdf será expandido para a lista de arquivos correspondentes. No entanto, se $HOME continha caracteres curinga, eles também poderiam ser expandidos, e é por isso que ainda é preferível usar uma variável de matriz.

    
por 18.11.2013 / 13:13
4

Você pode ter visto coisas como files=$* e files=~/print/*.pdf em shells mais antigos sem arrays e, em seguida, ls $files .

Uma substituição de variável que não está entre aspas duplas interpreta o valor da variável como uma lista separada por espaço em branco de padrões de curingas de shell que são substituídos por nomes de arquivos correspondentes, se houver algum. Por exemplo, depois de files=~/print/*.pdf , ls $files expande para algo como ls com os argumentos /home/highsciguy/print/bar.pdf , /home/highsciguy/print/foo.pdf , etc. No caso files=$* , essa atribuição concatena os argumentos passados para o script com espaços em entre e ls $files os divide de volta.

Tudo isso se divide se você tiver nomes de arquivos contendo espaços em branco ou caracteres globbing, e é por isso que você não deve fazer as coisas dessa maneira. Use matrizes em vez disso.

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

Note que

  • Todas as atribuições de matriz exigem parênteses em torno dos valores da matriz: var=(…) .
  • Para testar se uma matriz está vazia, verifique seu tamanho. "$files" está vazio quando files é uma matriz cujo elemento do índice 0 não está definido ou está vazio. Além disso, [ "X$foo" = "X" ] é uma maneira obsoleta de testar se $foo está vazio: todos os shells modernos implementam [ -n "$foo" ] corretamente. No bash, você pode usar [[ -n $foo ]] .

Em shells que não suportam arrays, existe de fato um array: os parâmetros posicionais para o shell ou função atual. Aqui, você realmente não precisa da matriz files , na verdade, seria mais fácil usar os parâmetros posicionais.

#!/bin/sh
if [ "$#" -eq 0 ]; then
  set -- ~/print/*.pdf
fi
for file do …
    
por 19.11.2013 / 01:27