Bash globbing e passagem de argumentos

8

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 arquivos) como parâmetros, esse script imprimirá os nomes de arquivos adequados. Por outro lado, se eu não passar argumentos, ele imprimirá

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

Por que os nomes de arquivo não são expandidos nesse caso e como corrigi-lo? Observe que eu uso as construções files=("$@")e "${files[@]}"porque li que é preferível aos "arquivos = $ *" usuais.

highsciguy
fonte
Onde é files=$*sempre usual ? Isso está errado .
Stéphane Chazelas
O usual é relativo, certo. Eu quis dizer um método que não usa matrizes. O que você faria então?
highsciguy

Respostas:

10

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

No

 files=$HOME/print/*.pdf

Você está atribuindo uma string como /home/highsciguy/print/*.pdfà $filesvariável escalar (também conhecida como string).

Usar:

files=(~/print/*.pdf)

ou

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

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

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

Você não precisa usar recursos sh não-padrão e pode usar o sistema em shvez de bashaqui escrevendo-o:

#!/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 com que a concha expanda a glob no momento em que a $files variável for expandida.

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

Aqui, como $filesnão é citado (o que você normalmente não deve fazer), sua expansão está sujeita à divisão de palavras (que desativamos aqui) e geração de globbing / nome de arquivo.

Portanto, o *.pdf será expandido para a lista de arquivos correspondentes. No entanto, se $HOMEcontiverem caracteres curinga, eles também poderão ser expandidos, motivo pelo qual ainda é preferível usar uma variável de matriz.

Stéphane Chazelas
fonte
4

Você pode ter visto coisas como files=$*e files=~/print/*.pdfem conchas mais antigas, sem matrizes, e então ls $files.

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

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

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

Observe que

  • Todas as atribuições de matriz requer parênteses em torno dos valores da matriz: var=(…).
  • Para testar se uma matriz está vazia, verifique seu comprimento. "$files"está vazio quando filesé uma matriz cujo elemento do índice 0 está desmarcado ou uma sequência vazia. Também [ "X$foo" = "X" ]é uma maneira obsoleta de testar se $fooestá vazio: todas as conchas modernas são implementadas [ -n "$foo" ]corretamente. No bash, você pode usar [[ -n $foo ]].

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

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