Bash Globbing и передача аргументов

8

У меня есть следующий упрощенный скрипт bash

#!/bin/bash

files=("$@")

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

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

Если я передам аргументы (имена файлов) в качестве параметров, этот скрипт выведет правильные имена файлов. С другой стороны, если я не передам аргументы, он напечатает

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

Почему в этом случае имена файлов не раскрываются и как это исправить? Обратите внимание , что я использую files=("$@")и "${files[@]}"конструкции , потому что я читал , что это должно быть более предпочтительным , чем обычные «файлы = $ *».

highsciguy
источник
Где files=$*когда - либо обычно ? Это совершенно неправильно .
Стефан Шазелас
Обычное относительно, верно. Я имел в виду метод, который не использует массивы. Что бы вы сделали тогда?
Highsciguy

Ответы:

10

Вы назначаете filesв качестве скалярной переменной вместо переменной массива .

В

 files=$HOME/print/*.pdf

Вы назначая некоторую строку , как /home/highsciguy/print/*.pdfна $filesскаляр (ака строка) переменной.

Использование:

files=(~/print/*.pdf)

или

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

вместо. Оболочка раскроет этот шаблон глобинга в список путей к файлам и назначит каждый из них элементам $files массива .

Расширение шара выполняется во время назначения.

Вам не нужно использовать нестандартные функции sh, и вы могли бы использовать свою систему shвместо этого bash, написав это:

#!/bin/sh -

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

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

setназначить "$@"массив позиционных параметров.

Другой подход мог бы заключаться в том, чтобы хранить шаблон смещения в скалярной переменной:

files=$HOME/print/*.pdf

И пусть оболочка расширяет глобус во время раскрытия $files переменной.

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

Здесь, поскольку $filesон не заключен в кавычки (что обычно не следует делать), его расширение подвержено разбиению по словам (которое мы здесь отключили) и генерации глобализации / имени файла.

Таким образом, *.pdf будет расширен список подходящих файлов. Однако, если они $HOMEсодержат подстановочные знаки, их тоже можно развернуть, поэтому все же предпочтительнее использовать переменную массива.

Стефан Шазелас
источник
4

Возможно, вы видели такие вещи, как files=$*и files=~/print/*.pdfв старых оболочках без массивов, а затем ls $files.

Подстановка переменной, которая не находится в двойных кавычках, интерпретирует значение переменной как список разделенных пробелами шаблонов подстановочных знаков оболочки, которые заменяются соответствующими именами файлов, если они есть. Например, после того files=~/print/*.pdf, ls $filesрасширяется к чему - то , как lsс аргументами /home/highsciguy/print/bar.pdf, /home/highsciguy/print/foo.pdfи т.д. В случае files=$*, это назначение объединяющее аргументы , передаваемые в сценарий с пробелами между ними, и ls $filesрасколов их обратно.

Все это ломается, если у вас есть имена файлов, содержащие пробельные символы или символы Globbing, поэтому вы не должны так поступать. Вместо этого используйте массивы.

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

Обратите внимание, что

  • Все задания массива требуют скобок вокруг значения массива: var=(…).
  • Чтобы проверить, является ли массив пустым, проверьте его длину. "$files"пусто, когда filesявляется массивом, элемент индекса 0 которого не установлен, или пустой строкой. Также [ "X$foo" = "X" ]есть устаревший способ проверить, $fooявляется ли пустым: все современные оболочки работают [ -n "$foo" ]правильно. В Bash вы можете использовать [[ -n $foo ]].

В оболочках, которые не поддерживают массивы, фактически существует один массив: позиционные параметры оболочки или текущей функции. Здесь вам не нужен filesмассив, на самом деле было бы проще использовать позиционные параметры.

#!/bin/sh
if [ "$#" -eq 0 ]; then
  set -- ~/print/*.pdf
fi
for file do 
Жиль "ТАК - перестань быть злым"
источник