Ловушки bash
Программирование на shell в общем и на bash в частности богато своими нюансами, которые, зачастую, упускаются из вида. В результате мы имеем проблемы на очевидных, вообщем-то, операциях. И как результат, зачастую, бывает “а ну его, этот баш! Перепишу на php/perl/python/ruby/etc”
Эта статья написана для обсуждения и путей решения нескольких самых часто встречающихся “камней преткновения” при программировании на bash. Я лично очень полюбил программировать на bash в последнее время и хочу поделиться кусочком знаний с вами :)
1. for i in `ls *.mp3`
Знакомо? :) Если в имени файла встретится пробел, то все ваши усилия будут напрасны. Каждая из составляющих имени попадет в отдельную итерацию.
for i in `ls *.mp3`; do # Неверно!
some command $i # Неверно!
done
Не получится и “закавычить” вывод ls
for i in "`ls *.mp3`"; do # Неверно! ...
В этом случае ВЕСЬ вывод ls будет рассматриваться в контексте одной итерации. Это немного не то, чего хотелось бы добиться :) Решение есть
for i in *.mp3; do # Надо делать вот так и... some command "$i" # ...во втором пункте мы рассмотрим и это "узкое" место. done
2. cp $file $target
Если в $file или $target окажутся пробелы, то вас ждет разочарование :)
Выход не менее очевиден
cp "$file" "$target"
3. Имена файлов, начинающиеся с дефисов
Всем известно, что параметры многих команд начинаются с дефиса -. В том случае, если с дефиса начинается имя файла, то оно будет ошибочно воспринято как параметр и вы получите ошибку. В лучшем случае.
Одно из решений – поместить перед именами передаваемых фалов два дефиса --. Это сигнализирует команде (например cp) о том, что список параметров закончен и дальше идут аргументы:
cp -- "$file" "$target"
Но более элегантным решением, все-таки, будет цикл (причем с указанием каталога в пути к файлу):
for i in ./*.mp3; do cp "$i" /target ...
В этом случае аргумент, начинающийся с дефиса, будет передан как ./-foo.mp3 и все сработает нормально.
4. [ $foo = "bar" ]
В bash вам необходимо заботиться о своих переменных. Иначе получите кучу ошибок :) Пример из заголовка выдаст ошибку в двух случаях:
- Если переменная, переданная в
[не объявлена или пустая, то команда["увидит" выражение
[ $foo = "bar" ]
как
[ = "bar" ]
и вы получите ошибку unary operator expected (ожидается унарный оператор). Так как оператор
=бинарный, то[будет несколько шокирована :) - Если в
$fooсодержаться пробелы, то сравнение также будет некорректным
[ multiple words here = "bar" ]
И если вам это может показаться нормальным, то для
[это довольно неожиданно :)
Более корректно будет записать выражение как
[ "$foo" = bar ] # Все отлично
но, опять-таки, выполучите ошибку, если текст в переменной начинается с -
В bash есть ключевое слово [[, которое является расширением старой команды test, также известной как [ и это решение всех подобных проблем :)
[[ $foo = bar ]] # Правильно
В случае с использованием [[ ]] вам не надо заключать переменную в кавычки, так как эта конструкция корректно обрабатывает и пустые переменные, и переменные, содержащие пробелы, и переменные, значение которых начинается с дефиса.
Также вам может встретиться вот такой вариант:
[ x"$foo" = xbar ]
x"$foo" - это хак для старых версий шелла, в которых вы вынуждены использовать [. И чтобы позаботиться о значении переменных, наичнающихся с дефиса, то можно использовать вот такую конструкцию.
А если одна из сторон сравниваемого выражения константа, то просто поместите переменную в правую часть :) [ не обращает внимания на то, что находится справа
[ bar = "$foo" ]
Только левая часть выражения требует вашего особого внимания, на случай значения, начинающегося с дефиса.
Продолжение следует :)
Статья супер! Жду продолжения.
Спасибо. Было полезно
Какие-то странные откровения вы выдаёте. Всё это изложено в ‘Advanced Bash Scripting Guide’
tldp.org/LDP/abs/html/
Имеется и русский перевод, датированный 2003 годом.
Да все когда-нибудь где-нибудь изложено :) Если вам проще, то считайте это выжимками из ABS
for i in *.mp3 – выдаст ошибку при отсутствии mp3-файлов
Название у статьи неоднозначное. Идя по линку сюда , я ожидал почитать о bash traps. хм!!!
А еще бывают переводы строки в имени файла. Взгуглил весь интернет – нормального решения не нашел.
И еще это решение не подходит, если нужно что-то посложнее чем *.mp3, например обработать вывод find.
Пытался сделать через while read … done < `find …` – тут оказалось что весь цикл в сабшелле и переменные оттуда хер достанешь. Спустя много дней перечитывае абс-гайд нашел конструкцию {…}<… , которая не порождает сабшелла, может кому поможет, но сам не пробовал и даже пытаться неохота.
В обещм, а ну его, этот баш! Лучше не связываться, если есть возможность.