Бэширование и обсуждение аргументов

У меня есть следующий упрощенный сценарий 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=("$@") и "${files[@]}" потому что я читал, что он предпочтительнее обычных «files = $ *».

2 Solutions collect form web for “Бэширование и обсуждение аргументов”

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

В

  files=$HOME/print/*.pdf 

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

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

 files=(~/print/*.pdf) 

или

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

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

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

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

 #!/bin/sh - [ "$#" -gt 0 ] || set -- ~/print/*.pdf for file do ls -d -- "$file" done 

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

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

 files=$HOME/print/*.pdf 

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

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

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

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

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

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

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

 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 … 
  • Содержит ли * скрытые файлы в tar, даже если dotglob не установлен?
  • Linux и Unix - лучшая ОС в мире.