Bash menggumpal dan melewati argumen

8

Saya memiliki skrip bash yang disederhanakan berikut ini

#!/bin/bash

files=("$@")

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

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

Jika saya memberikan argumen (nama file) sebagai parameter, skrip ini akan mencetak nama file yang tepat. Di sisi lain, jika saya tidak memberikan argumen, itu akan dicetak

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

Mengapa nama file tidak diperluas dalam kasus ini, dan bagaimana cara memperbaikinya? Perhatikan bahwa saya menggunakan files=("$@")dan "${files[@]}"membangun karena saya membaca bahwa itu lebih disukai daripada "files = $ *" yang biasa.

kelas tinggi
sumber
Dimana files=$*pernah biasa ? Itu salah .
Stéphane Chazelas
Biasa adalah relatif, benar. Maksud saya metode yang tidak menggunakan array. Apa yang akan kamu lakukan?
highsciguy

Jawaban:

10

Anda menetapkan filessebagai variabel skalar alih-alih variabel array .

Di

 files=$HOME/print/*.pdf

Anda menetapkan beberapa string seperti /home/highsciguy/print/*.pdfke $filesvariabel skalar (alias string).

Menggunakan:

files=(~/print/*.pdf)

atau

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

sebagai gantinya. Shell akan memperluas pola globbing ke daftar path file, dan menetapkan masing-masing ke elemen $files array .

Perluasan glob dilakukan pada saat penugasan.

Anda tidak harus menggunakan fitur sh non-standar, dan Anda bisa menggunakan sistem Anda shalih - alih di bashsini dengan menuliskannya:

#!/bin/sh -

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

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

setadalah untuk menetapkan "$@"berbagai parameter posisi.

Pendekatan lain bisa saja menyimpan pola globbing dalam variabel skalar:

files=$HOME/print/*.pdf

Dan memiliki shell memperluas gumpalan pada saat $files variabel diperluas.

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

Di sini, karena $filestidak dikutip (yang biasanya tidak Anda lakukan), perluasannya tunduk pada pemisahan kata (yang kami nonaktifkan di sini) dan pembuatan globbing / namafile.

Jadi *.pdf akan diperluas ke daftar file yang cocok. Namun, jika $HOMEberisi karakter wildcard, mereka dapat diperluas juga, itulah sebabnya mengapa masih lebih disukai untuk menggunakan variabel array.

Stéphane Chazelas
sumber
4

Anda mungkin telah melihat hal-hal seperti files=$*dan files=~/print/*.pdfdi kulit yang lebih tua tanpa array, dan kemudian ls $files.

Substitusi variabel yang tidak berada dalam tanda kutip ganda menafsirkan nilai variabel sebagai daftar pola wildcard shell yang dipisahkan dengan spasi putih yang diganti dengan nama file yang cocok jika ada. Misalnya, setelah files=~/print/*.pdf, ls $filesmemperluas ke sesuatu seperti lsdengan argumen /home/highsciguy/print/bar.pdf, /home/highsciguy/print/foo.pdfdll. Dalam kasus files=$*ini, tugas ini menggabungkan argumen yang diteruskan ke skrip dengan spasi di antaranya, dan ls $filesmembaginya kembali.

Semua ini rusak jika Anda memiliki nama file yang mengandung karakter spasi atau globbing, itulah sebabnya Anda tidak boleh melakukan hal-hal seperti ini. Gunakan array sebagai gantinya.

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

Catat itu

  • Semua tugas array yang membutuhkan tanda kurung di sekitar nilai array: var=(…).
  • Untuk menguji apakah array kosong, periksa panjangnya. "$files"kosong ketika filesarray yang elemennya indeks 0 tidak disetel atau string kosong. Juga [ "X$foo" = "X" ]merupakan cara usang untuk menguji apakah $fookosong: semua shell modern menerapkan [ -n "$foo" ]dengan benar. Di bash, Anda bisa menggunakan [[ -n $foo ]].

Dalam shell yang tidak mendukung array, sebenarnya ada satu array: parameter posisi ke shell atau fungsi saat ini. Di sini, Anda tidak benar-benar membutuhkan filesarray, sebenarnya akan lebih mudah untuk menggunakan parameter posisi.

#!/bin/sh
if [ "$#" -eq 0 ]; then
  set -- ~/print/*.pdf
fi
for file do 
Gilles 'SANGAT berhenti menjadi jahat'
sumber