Comments 123
С другой стороны, даже в переводе его изучать не особо хочется.
#!/bin/bash
Позанудствую: во FreeBSD баш пихают в /usr/local/bin/bash
, поэтому такой скрипт там не запустится. Так что теперь я пишу #!/usr/bin/env bash
(правда, где-то читал, что это тоже где-то может не работать, но я уже забыл где)
Думается мне, лезть в системные файлы, которые для лазания не предназначены, не очень хорошо
В Linux все системные файлы для того и нужны — чтоб было что ковырять)
Было бы интересно почитать про развертывание приложений при помощи bash-скриптов
$((1+1))намного читабельнее использовать
$[1+1]
if grep $user /etc/passwdнадо либо -q, либо вывод перенаправить.
if grep $user /etc/passwd
надо либо -q, либо вывод перенаправить
Все 3 способа неправильные ;)
Либо резать на поля и искать (awk), либо взять готовую утилиту id
if id -u "$user" 2>&1 > /dev/null
then
, но это уже не поиск в passwd, а нечто большее.
Зависит от того, что хотели получить.
Просим гуру bash-программирования рассказать о том, как они добрались до вершин мастерства, поделиться секретами, а от тех, кто только что написал свой первый скрипт, ждём впечатлений.
Не читайте про bash на хабре, читайте man, --help и книги!
Кстати, я думал что [ — это симлинк на test, но в моей системе это разные программы:
lorc:work/ $ ls -l /usr/bin/\[
-rwxr-xr-x 1 root root 51920 лют 18 2016 /usr/bin/[
lorc:work/ $ ls -l /usr/bin/test
-rwxr-xr-x 1 root root 47824 лют 18 2016 /usr/bin/test
Нужно либо задавать полное имя, либо использовать другой shell (например /bin/sh), где эти команды не устроены…
Очень рекомендую использовать https://github.com/koalaman/shellcheck для проверки шелл скриптов как часть CI, ловит как простые, так и сложные штуки, и помогает не забывать про корнер кейсы на разных ОС, офигенный тул.
Действительно классная штука, но всегда полагаться на её предупреждения не стоит, ибо она не достаточно проницательна, чтобы понять какое поведение тебе нужно или некоторые сложные случаи.
Кстати, в некоторых дистрибутивах она доступна в репе (например в Ubuntu: sudo apt install shellcheck
).
Есть плагин для SublimeText: SublimeLinter-shellcheck
Стоит отметить, что часто со второго-третьего раза, перечитывая описания предупреждений в их вики, таки находится более правильный вариант, который успокаивает и автора скрипта и shellcheck :)
Ну и у shellcheck есть формат комментариев для сапреса ворнингов.
А, кстати, на шелл скрипты можно и тесты писать https://github.com/gojuno/mainframer (см папку test
и travis.yml
), выглядит примерно так:
я просто оставлю это здесь https://github.com/denysdovhan/bash-handbook
Прочитал ABS Guide. Периодически использую его как справку. Всё.
P.S. Сам факт, что в bash'е эта задача весьма нетривиальна вызывает лёгкую грусть, конечно…
declare A=([a]='x' [b]='y')
Копируйте.P.S. Эта проблема в bash4 появилась. В bash2 было так:
$ declare -a A=('a' 'b c' 'd e')
$ B=("${A[@]}")
$ declare -p B
declare -a B='([0]="a" [1]="b c" [2]="d e")'
Просто, логично, понятно. А теперь, в bash4, чего делать? Есть несколько решений, но красивого я не знаю… что грустно: что это за язык такой, в котором переменную скопировать — проблема?В bash операции со строками имеют особенности, а массивов, по возможности, лучше избегать. Простл запомнить это. Если нужны какие-то структуры данных — лучше взять python.
В С, чтобы скопировать массив, тоже надо пару раз присесть ;)То что массивы в C сделаны неудобно и криво — это всем известно. И оправданием для bash являться никак не может.
А массивы в bash нужны. Как без них параметры командной строки обрабатывать?
while [ -n "$1" ]
do
_param="$1"
shift
# флажки без параметров
[ "$_param" = "-v" ] && verbose=1 && continue
[ "$_param" = "-d" ] && debug=1 && continue
# с параметрами
if [ "$_param" = "-i" ]; then
input="$1"
shift
continue
fi
done
Где-то так. Например.
Напишите, скажем, скрипт с названием gcc, например, который получает аргументы, находит среди них -c, и, если получается, то вызывает gcc.real дважды: один раз «как есть», а второй — заменяя в командной в имени файла после -o расширение с .o на .ii и вместо -c вставляя -E.
С использованием массивов это делается. Без них — будут проблемы если имена файлов или каталогов будут с пробелами, возвратами кареток, etc.
Замечу, в программистских исходниках очень редко попадаются имена файлов с возвратами кареток ;)
Замечу, в программистских исходниках очень редко попадаются имена файлов с возвратами кареток ;)Собственно это самая большая проблема с bash'ем: то, что «сходу» приходит на ум — как правило работает в 99% случаев. А потом в результате чьей-нибудь идиотской ошибки происходит
"mkdir -p `cat some-crazy-file`"
(что создаёт, вроде бы, безобидный пустой каталог и всё) и скрипты, «работавшие годами» вдруг взрываются…В С, чтобы скопировать массив, тоже надо пару раз присесть ;)
А в чём проблема то? memcpy же.
Но скриптовые языки, как бы, призваны сделать эти задачи простыми — пусть и ценой существенной потери производительности. Так вот bash тут — уникален: потери в производительности есть, ещё какие — а выигрыша в простоте использования нет!
И malloc. И рассчитать размер массива. И не забыть free
Это не только в C. Плюса, делфи, да много их таких.
А если массив указателей, и данные тоже нужно скопировать?
А это уже «отсебятинская» (по отношению к языку) структура данных. Опять таки, в большинстве языков их копирование вызовет аналогичные проблемы. Единственное, в C мало «не отсебятинских» структур.
Плюсовые массивы (aka векторы) копируются при присваивании.
Но речь не о C.
Стоит отметить, во многих языках сложные конструкции (в т.ч. и массивы) тягают по ссылке (суть указателю). Не понимаю, в чём проблема и в чём недостаток по сравнению с другими языками.
Не понимаю, в чём проблема и в чём недостаток по сравнению с другими языками.Во временах жизни. Если вы внутри функции заведёте массив и вернете его «по ссылке» — то это добром не кончится.
А в bash и передачи по ссылке нету…
Во временах жизни. Если вы внутри функции заведёте массив и вернете его «по ссылке» — то это добром не кончится.
Ну, правильно. Потому что массив статический. Сделайте динамическим и возвращайте как хотите. При этом, говоря о плюсах, векторы нормально таскаются туда-сюда.
При этом в чём неудобство передачи по ссылки в функцию мне до сих пор не ясно.
А в bash и передачи по ссылке нету…
На счёт bash'а не знаю, но не редко скриптовые языки делают массивы не на стеке, а в куче. При этом в функции и из функций таскается указатель на них. Так что в них просто немного дополнительного синтаксического сахара, ничего принципиально отличного нет.
Типичные массивы в C — T[], не поддерживают копирование при присваивании просто так.
В C++ типичные массивы — std::vector, копируются при присваивании. Если вас не устраивает термин массив, давайте назовем списком.
Если писать на C++, то использовать стоит вещи из C++, имхо.
Но функционал, который доступен в стандартном bash (posix), вполне интуитивен, и нетривиальные задачи, часто имеют более тривиальное решение. Но для этого нужно разбираться по существу.
Но функционал, который доступен в стандартном bash (posix), вполне интуитивен
В том-то и дело, что нет. Одну задачку мы уже обсуждали. Рассмотрим более простую: напишем скрипт, который мы, опять-таки, назовём gcc и хотим «подсунуть» в PATH. Мы хотим просто вызвать настоящий gcc добавив в командную строку опцию -V4.4.3 (предположим что система сборки у нас такая хитрая, что это в ней сделать сложно). Как нам нужно писать?
/path/to/gcc -V4.4.3 $*
Нет, так не годится. Нужно вот так:/path/to/gcc -V4.4.3 "$@"
Это — один из первых «костылей», которые были добавлены в bash для того, чтобы простое, понятное, но не работающее решение превратить в хитрое и странное — но работающее!В современном bash'е таких костылей — достаточно, и ассоциативные массивы — один из них. Они создают свои проблемы, но в старом bash'е жизнь, увы, не легче. Ибо этих костылей у вас нет и приходится извращаться. Там даже регулярных выражений нет у
[[ … ]]
!напишем скрипт, который мы, опять-таки, назовём gcc
А не проще в таком случае создать alias?
"$@"
, то никаких зависимостей не будет. Проблема в том, что «интуитивный» и «естественный» код не работает.Впрочем скажу одну вещь и в защиту bash'а сказать: пусть и с «некрасивым» синтаксисом, пусть и «странно» — но в нём эта задача делается. А вот в Windows мы не нашли ни одного встроенного инструмента, позволяющего это сделать. На cmd, ни powershell решить эту задачу не позволяют. Пришлось враппера на C писать в своё время…
Ну, или я задачку не совсем понял.Задачку вы, я думаю, поняли — вы пробелемы не поняли.
Мне нужен «псевдо-gcc», который вызывает «настоящий» gcc с добавлением в командную строку пары аргументов. Всего-навсего. Чтобы можно было подсунуть его в произвольную систему сборки.
Кажется можно сделать просто gcc.cmd и всё? А вот и нет: что будет если система сборки вызывает какой-нибудь helper.cmd:
gcc %*Что будет если у вас в первой строке вызовется gcc.cmd? Правильно —
@copy /B %1.done+,, %1.done
touch
уже не отработает.Та же самая проблема с PowerShell: если система сборки использует не ShellExecute, а WinExec — то ваш скрипт работать не будет!
P.S. Конечно дальше выясняется, что и просто взять и написать программу на C тоже нельзя, но это — другая история.
А не проще в таком случае создать alias?Нет, не проще. Алиасы работают только «внутри» bash'а, программы, вызванные из него их «не видят».
Из документации, как раз следует, что нужно $@, и это не костыль а как раз правильное использование.
https://www.gnu.org/software/bash/manual/bashref.html#Special-Parameters
Что же касается кавычек, то это и понятно — вы хотите, чтобы встреченные wildcards были раскрыты в вашем скрипте, или во время выполнения команды?
ВСЕ логично. Просто после вдумчивого чтения документации, интуиция работает значительно лучше.
Не понимаю, с чего вы взяли что нужно $*?Потому что только так и можно было использовать переменные в V6. Внутри кавычек они не раскрывались, а для передачи аргументов (до 9) нужно было писать
$1 $2 $3 $4 $5 $6 $7 $8 $9
— почти как в DOS.А в V7 вспомнили про то, что в именах файлов бывают пробелы и сделали так, что позиционные аргументы стали раскрываться не только вне кавычек, но и внутри. Но при этом
"$*"
вместо DOS-стиля "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9"
— использовать оказалось нельзя, так как все аргументы сливались в кучу. Появился первый костыль — в добавление к простому, понятному, но не всегда работающему $*
— сделали ещё и "$@"
.Что же касается кавычек, то это и понятно — вы хотите, чтобы встреченные wildcards были раскрыты в вашем скрипте, или во время выполнения команды?Во время исполнения команды wildcards не раскрываются в Unix.
ВСЕ логичноНелогично то, что любая задача решается 5-10 способами, причём первые 3-7 самых простых — работают не всегда. Такая, немножко иезуинткая логика: «а вы не забыли, что в именах файлов могут быть пробелы? ага — а тут у нас костылик надо использовать! а про то, что имена файлов могут начинаться с дефиса не забыли? ну как же — ещё один костылик нужен! а про перевод каретки в имени файла? ну как же без 3го костылика-то?»
Просто после вдумчивого чтения документации, интуиция работает значительно лучше.Вдумчивое чтение документации позволяет вам достаточно уверенно решать ребусы, которыми являются bash-скрипты. Но не делает написание этих скриптов простым занятием всё равно.
Простейший пример:
$(…)
— это всего лишь замена на `…`
? И вроде как их пожно просто заменять друг на друга? Да? Но ведь нет:$ echo "`echo "\\\\"`"
\
echo "$(echo "\\\\")"
\\
«Вдумчивое чтение документации», конечно, обьяснит в чём разница — но логичней всю эту коллекцию костылей не сделает…Простейший пример: $(…) — это всего лишь замена на `…`?
Не очень удачный пример — конструкция $(...) появилась именно для того, чтобы быть нагляднее и решать конкретно этот случай, когда вам нужно вложить подстановку внутри подстановки, в остальном они равноправны.
Проблемы в вашей билд системе, IMHO заключается в том, что вы хотите своими баш скриптами запускать чужие баш скрипты, а про совместимость никто не думал.
Я могу ошибаться, но можно переходить на современные системы типа maven и всех проблем избежать. То есть IMHO зря наезжаете на баш.
Если брать именно os-related язык, то bash — гораздо лучше чем cmd/powershell и другие подобные языки.
Вы ругаетесь на проблемы, которые были в V6 (1975 год) и V7(1979 год)?Я ругаюсь на язык, который содержит в себе большую коллекцию проблем и большую коллекцию костылей, предназначенных для обхода этих проблем. И в котором нужно «решать ребусы» при написании программ, так как простые решения — как правило не на 100% работоспособны.
Не очень удачный пример — конструкция $(...) появилась именно для того, чтобы быть нагляднее и решать конкретно этот случай, когда вам нужно вложить подстановку внутри подстановки, в остальном они равноправны.Точно так же как $@ появилась чтобы решить проблему с использованием позиционных аргументов в кавычках, собственно. И также как
[[ … ]]
появился чтобы решиьть прблемы с [ … ]
. Но при этом старые, «проблемные» механизмы никуда не делись, и, собственно, узнать о том, как нужно делать «правильно» зачастую неоткуда. Потому что в том же мануале описана разница между `…`
и $(…)
, но вот зачем эта разница нужна — ничего не сказано.Проблемы в вашей билд системе, IMHO заключается в том, что вы хотите своими баш скриптами запускать чужие баш скрипты, а про совместимость никто не думал.Проблема не билд-системе.
Я могу ошибаться, но можно переходить на современные системы типа maven и всех проблем избежать.Интересная идея. У тебя спрашивают — как пользоватьтся вашим компилатором, а ты и отвечаешь «да никуда не годятся все ваши GNU Make, Ninja и прочие SCONS'ы, если вы хотите возпользоваться нашим чудо-компилятором — то maven и только maven, там всё будет работать».
Я боюсь начальство такое решение проблемы не одобрит, однако. И будет право.
Если брать именно os-related язык, то bash — гораздо лучше чем cmd/powershell и другие подобные языки.Однако python (если его можно использовать) — ещё лучше!
Shell Style Guide от Google
Тот самый Advanced Bash-Scripting Guide
Учебное пособие на eddnet.org
Тред на StackOverflow о скрытых фичах bash
Полезные одно-строчные скрипты sed
Ну и man bash
периодически покуривать имея ввиду, что на удалённом хосте версия bash может отличаться и некоторые функции могут быть [не]доступны.
Всегда пишу расширение для скриптов. Может немного старомодно, но хотя бы нет путаницы: увидел myscript.sh и сразу понял какой интерперетатор используется.
.sh — внутри скрипт на shell, скорее всего совместим с bash/ash/dash
.bash — внутри башизмы, может не запуститься на произвольном shell без напильника.
Если не указан shabang с интерпретатором, запустится в том, откуда вызываете.
Упражнение на дом: что выведет последний скрипт если в переменной $mydir будет начинающееся с пробела имя существующей директории?
Именно. Убежден, что изучение командной оболочки *nix надо начинать с осознания таких вот драматических несоответствий в её дизайне. Типа:
— О, давай у нас оболочка сама будет шаблоны разворачивать!
— Круто!… Эй, подожди, у нас же имена файлов могут содержать почти любые символы, включая * и -?!!!
— Ну и что? В 99% случаев будет работать, да и ладно!
Чтобы потом не было мучительно больно за скрипт, выполнивший немного не то, что ты рассчитывал.
Bash гораздо функциональнее чем описанные здесь примеры.
Я уже не говорю о том, что вы пишите ужасный код, и некоторые привычки могут рано или поздно вылиться Вам боком, например использование [, вместо [[, или использование `$a', вместо `a' внутри $(( )). Конечно всё это рабочие варианты, если помнить о разных нюансах, но тогда не надо статью озаглавливать как «Bash-скрипты».
http://mywiki.wooledge.org/BashFAQ
http://mywiki.wooledge.org/BashGuide
http://wiki.bash-hackers.org/
echo -e '#!/bin/bash\necho $$\n' > tryit.sh
chmod 750 !$
./tryit.sh
./tryit.sh
./tryit.sh
source ./tryit.sh
source ./tryit.sh
source ./tryit.sh
ну а во второй части уже пора бы узнать что такое $$, $! и т.д.
И почему моя echo команда не будет работать с двойными кавычками…
str1 < str2Возвращает истину, если str1меньше, чем str2.
А какая строка меньше "десять" или "тыща"? Совершенно непонятно ЧТО сравнивается в строках, а потом статья говорит что можно еще и сортировать с помощью этого оператора. WAT??? Сравниваются ASCII-коды символов?? Как можно сравнить два массива чисел разной длины? Может всё-таки по размеру сначала, а при равенстве размера какая-то еще логика? Вот этот вопрос не ясен...
Обратите внимание на то, что скрипт, хотя и выполняется, выдаёт предупреждение:
./myscript: line 5: [: too many arguments
Для того, чтобы избавиться от этого предупреждения, заключим $val2 в двойные кавычки:
Здесь пропущен архи-важный ответ на вопрос "зачем?". Я не понимаю как bash интерпретировал выражение и ПОЧЕМУ кавычки эту интерпритацию меняют. Вот эти все сравнения строк для меня всегда были какой-то черной магией с десятком разных подходов и Ваша статья еще сильнее убеждает меня в том что так и есть.
Я даже проверить не смог после этой статьи… Не хватает хороших мануалов по bash, не хватает…
root@W10:~# "десять" \> "тыща"
десять: command not found
root@W10:~# if [["десять" \> "тыща"]] echo true
>
> fi
bash: syntax error near unexpected token `fi'
root@W10:~# if [["десять" \> "тыща"]]; echo true; fi
bash: syntax error near unexpected token `fi'
root@W10:~# if [["десять" \> "тыща"]] then echo true; fi
bash: syntax error near unexpected token `fi'
root@W10:~# if [["десять" \> "тыща"]] then echo true fi
> ;
bash: syntax error near unexpected token `;'
root@W10:~# if [["десять" \> "тыща"]] then; echo true; fi
bash: syntax error near unexpected token `fi'
root@W10:~# if ["десять" \> "тыща"] then; echo true; fi
bash: syntax error near unexpected token `fi'
root@W10:~#
if ["десять" \> "тыща"] then; echo true; fi
if [ "десять" \> "тыща" ]; then echo true; fi
Найдите 3 отличия ;)
ну да, я быстро сдался, спасибо. Надеюсь, запомню где там обязательные разрывы а где необязательные...
[ — это команда с параметрами. Параметры разделяют пробелами.
Вот теперь я вижу логику языка, спасибо. А когда после then вместо пробела разрыв строки, это больше похож на какую-то особую конструкцию языка чем на три команды с параметрами.
Как можно сравнить два массива чисел разной длины?
strcmp, strcmpi… Можно.
Зачем? Хотя бы чтобы был критерий для сортировки ;)
Для того, чтобы избавиться от этого предупреждения, заключим $val2 в двойные кавычки:
Это особенная, bash'евская магия! Если в $val2 пустая строка — в оператор сравнения не передается один из параметров. А если в $val2 строка с пробелами — передастся N параметров. Если пустая строка, но в кавычках — передастся пустая строка. А если в одинарных кавычках — переменные внутри кавычек не преобразуются в значения.
КМК очень, очень плохая идея — обрабатывать строки в bash. Разве что от безысходности. perl создан для этого ;).
Совершенно непонятно ЧТО сравнивается в строках, а потом статья говорит что можно еще и сортировать с помощью этого оператора.А как строки сравниваются в других языках — вам понятно? Pascal, Pyhton, C? stroll? Толковый словарь Ожегова?
Я не уверен что задачей статьи было обучить писать скрипты человека, который о программировании не знает ничего вообще — и гордится этим.
Лексикографический порядок не является единственным возможным вариантом. Сходу нагуглился как минимум Kleene–Brouwer order. Кроме того, статья позиционируется как обучающая и совершенно точно не должна оставлять таких вопросов (ссылки на википедию было бы более чем достаточно). Я радикально не согласен с тем что задачей статьи не является "обучить писать скрипты человека, который о программировании не знает ничего вообще", потому что для автоматизции простых задач совершенно не требуется знать что такое лексикографический порядок и что на самом деле "десять" > "тыща"
.
К огромному сожалению оба посыла ложны: писать скрипты на bash сложно и, как правило, не нужно: есть много других языков, где решение простейших задач не превращается в ребус.
А если вас необходимо писать скрипты по той или иной причине, то вы, скорее всего уже не один язык программирования знаете и рассказывать вам про лексикографический порядок — не нужно от слова «совсем»…
P.S. Если же ваша задача не «написать скрипт, который работает независимо от того, есть ли в имени файла, с которым он работает, возврат каретки или нет», а «написать скрипт, который, как правило, работает — если звёзды стоят правильно», то тут и вообще никакой серии статей не нужно: SODD вполне работает, если вас устраивает не вполне гарнтированный результат — зачем ещё и статьи какие-то?
Вот сейчас Вы правы, я считал что bash позиционируется как "простой" язык. Если в реальности bash-скриптинг — это действительно более ребусы, чем продуктивность, то да, лучше на том же питоне писать (лично я так и делаю). Но все-таки, мне всегда казалось что я что-то упускаю… Ведь именно bash является стандартной оболочкой всех современных линуксов. Создаётся впечатление, что это стандарт отрасли и вообще говоря у него большие шансы стать первым языком у юзера хотя бы потому что ему поневоле приходится писать apt-get
и dpkg -i
.
Если необходимо писать и есть конкретная задача, то решить её при помощи Гугла или даже man — никакого труда не составит. А статья нужна чтобы узнать как правильно, надёжно и без хаков писать рутинные вещи типа if. И перестать тратить время на выяснение этого каждый раз. Хотя, про то что это именно та статья, которая будет находится по запросам в Гугл, я не подумал. Но судя по количеству неточностей в статье, которые раскрыты коментами, не уверен что это хорошо.
как правильно, надёжно и без хаков писать рутинные вещи типа if
man же. Кроме if, есть и другие конструкции.
Ну, давайте не будем писать обучающие статьи вообще. И переводить man тоже не нужно, каждый кто общается с консолью обязан знать английский. И nano выпилить из всех дистрибутивов, только sed и vim, пусть как хотят так и колупаются, nano не тру. Повысим порог вхождения до небес, nobody needs lamers.
Также полезно его знать если вы используете make и тому подобные вещи.
В обоих случаях у вас, во-первых, не так много выбора, а во-вторых, так как вы работаете с чем-то, что заслуживает доверия, то для вас не так важно писать скрипты, умеющие работать с файлами, названия которых содержат странные символы,
Во всех остальных случаях, увы, bash использовать не стоит.
Создаётся впечатление, что это стандарт отрасли и вообще говоря у него большие шансы стать первым языком у юзера хотя бы потому что ему поневоле приходится писать apt-get и dpkg -i.Это «стандарт отрасли», потому что почти полвека назад его древний предок был стандартным shell'ом — и больше нипочему.
Если необходимо писать и есть конкретная задача, то решить её при помощи Гугла или даже man — никакого труда не составит.Составит, к сожалению.
Вот сейчас Вы правы, я считал что bash позиционируется как «простой» язык.Простой язык — это sh. Но многие вещи в нём, к сожалению, не делаются от слова «никак». А bash — это попытка добавить костылей, чтобы они таки делались. В результате — да, всё делается, всё как бы хорошо… вот только одна беда: из-за пресловутой «обратной совместимости» новые, работающие «костыльные» решения — неочевидны ни разу. А старые, «простые и понятные» — не работают!
Вот как, например, прочитать список строк, сохранённых в файле
filelist.zero-delim
и разделённых там символом с кодом 0 (потому что, блин, все остальные символы могут встречаться в именах файлов) и передать их в командной строке в вызове одной команды?Ну, например, так:
declare -a file_list
while IFS='' read -r -d '' file_name; do
file_list=("${file_list[@]:+${file_list[@]}}" "$file_name")
done < filelist.zero-delim
process "${file_list[@]:+${file_list[@]}}"
Офигительно просто, не так ли? Это вообще — выглядит как программа? Нет — это ребус.А 99% ответов, которые вы найдёте на просторах интернета будут решать эту задачу неправильно либо небезопасно (будут «взрываться» при использовании
set -o nounset
, например).В общем мой вам совет: используйте bash-скприты тогда, когда вы можете быть уверены что «враг» не передаст вам плохих данных (например если вы сами и пишите и используете их), либо если у вас нет выбора. Если выбор есть, то… лучше что-нибудь другое…
прочитать список строк, сохранённых в файле filelist.zero-delim и разделённых там символом с кодом 0 и передать их в командной строке в вызове одной команды?
1. xargs?
2. Кто-то ведь зачем-то создал этот файл? Можно было бы, наверное, передавать список и без файла, через конвейер?
3. tar, например, умеет получать список файлов из файла. Может, и не нужно решать эту задачу?
1. xargs?xargs позволит вам решить ровно эту задачу — и ничего более. Ни фильтрации, ни обработки, ничего.
2. Кто-то ведь зачем-то создал этот файл?Ну допустим его создали, скажем, использованием
find
с какими-то там параметрами — вам легче стало?Можно было бы, наверное, передавать список и без файла, через конвейер?От этого задача стала бы только сложнее, увы.
3. tar, например, умеет получать список файлов из файла. Может, и не нужно решать эту задачу?Во-первых он требует списка файлов разделённых
'\n'
— то есть с произвольными именами файлов работать не может. А во-вторых так мы дойдём до того, что будем писать программу на bash'е, которая выглядит как perl -e '...'
.Я весьма неплохо знаю bash, tar и прочие штуки, но давайте посмотрим правде в глаза: это корявые инструменты. Очень корявые. То, что мы научились с ними как-то жить — этой истины, увы, не отменяет…
Да, пример с find мне кажется натянутым. :)
Просто не надо делать на нем то, к чему он не приспособлен.Он «не приспособлен» работать со списками файлов, среди которых может встретится «нечто странное» (пробелы, хотя бы). Что, в общем, делает его мало пригодным для чего-либо вообще: в современном мире инструмент, который может, внезапно, сломаться, если у вас в каталоге обнаружится файл с «неправильным» именем — просто слишком опасен, чтобы рекомендовать его использовать где-бы-то-ни-было… Слишком велик риск…
#!/usr/bin/python2
import subprocess
subprocess.call(['process'] +
open('filelist.zero-delim').read().split('\0'))
Или Go:
package main
import (
"fmt"
"io/ioutil"
"os/exec"
"strings"
)
func main() {
file_list, err := ioutil.ReadFile("filelist.zero-delim")
if err != nil {
fmt.Print(err)
} else {
err = exec.Command(
"process",
strings.Split(string(file_list), "\000")...).Run()
if err != nil {
fmt.Print(err)
}
}
}
Вариант на Go, конечно, длиннее, но, в общем, вполне понятен и не напоминает ребус…
При этом find/xargs отлично работают с zero
При этом можно найти кучу других вариантов что можно с этим сделать.
Вот только прикол в том, что символ zero не очень текстовый, а баш изначально предполагает работу с текстом, поэтому вы нашли одну проблему, когда сложно работать с символом zero и делаете из этого неудобство?Неудобство из этого сделал не я, а создатели Unix, извините.
Они, с одной стороны, разработали систему, в которой в именах файлов могут встречаться любые символы, за исключением символа с кодом ноль (на слеши есть определённые ограничения, да, но они всё равно там могут быть, потому разделителем списка с именами файлов они быть не могут) — отсюда, собственно, все эти
xargs -0
, sort -z
и прочее.А с другой — они же создали инструмент, который плохо себя ведёт не только с этим символом, но и с возвратами кареток, пробелами и прочим.
При этом find/xargs отлично работают с zeroРаботают, да. Но «отлично» — я бы не сказал. Это всё равно не умолчание.
При этом можно найти кучу других вариантов что можно с этим сделать.Что? Я вижу только два варианта:
- Увеличить список запрещённых символов в именах файлов. Если исключить оттуда, скажем, возврат каретки — станет уже лучше. А если ещё и потребовать, чтобы имя файла было валидной UTF8-последовательностью — станет совсем хорошо.
- Отказаться от bash и использовать языки, для которых обработка таких файлов не представляет сложности.
Вариантов много: python, go, и масса других языков.
Так как сообщество в варианте #1 явно не заинтересовано (про это говорят уже десять лет, есть подробные предложения — однако никаких подвижек нет: никакого простого способа запретить создание таких файлов Linux по-прежнему не предлагает), то остаётся только вариант #2.
Или у вас есть какие-нибудь другие варианты? Где ваша «куча»?
P.S. Я про это уже писал: проблема bash не в том, что он чего-то не позволяет сделать. Проблема bash в том, очевидные подходы небезопасны, а безопасные подходы — неочевидны. Да, я понимаю, что это не злой умысел, а призрак Черномырдина… но от этого не легче. Лучше пользоваться средами, где очевидные — подходы безопасны, а безопасные — очевидны. Да, подводные камни есть везде, но bash… это просто клиника.
Bash-скрипты: начало