Как стать автором
Обновить

Комментарии 123

С одной стороны, man bash я до конца так и на дочитал.
С другой стороны, даже в переводе его изучать не особо хочется.
#!/bin/bash

Позанудствую: во FreeBSD баш пихают в /usr/local/bin/bash, поэтому такой скрипт там не запустится. Так что теперь я пишу #!/usr/bin/env bash (правда, где-то читал, что это тоже где-то может не работать, но я уже забыл где)

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

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

В Linux все системные файлы для того и нужны — чтоб было что ковырять)

Вас обманули. В системах на базе ядра Linux системные файлы нужны, что бы все нормально работало, именно по этой причине GNU/Linux лидирует на серверном рынке(а в TOP500 просто занимает 99.9% мест). А детишкам, которые руками лезут туда, куда писать должен пакетный менеджер место на своих локалхостах. Мамкиным какирам мамкино какерство!

Ага, именно поэтому видимо большинство системных настроек хранится в обычных текстовых файлах — очень уж удобно программам с ними работать. А главное, как производительно!

Делать симлинки в /bin это по плохому. Правильно, как вам уже выше написали #!/usr/bin/env bash, аналогично и #!/usr/bin/env python, #!/usr/bin/env perl и так далее.

Было бы интересно почитать про развертывание приложений при помощи bash-скриптов

а вот сейчас подленько было
  1. Баш
  2. Уводишь выполнение в докер как можно скорее
  3. ???
  4. Профит!
#!/usr/bin/env bash

exec ansible-playbook -i prod site.yml

как-то так

Вместо
$((1+1))
намного читабельнее использовать
$[1+1]

if grep $user /etc/passwd
надо либо -q, либо вывод перенаправить.
if grep $user /etc/passwd

надо либо -q, либо вывод перенаправить


Все 3 способа неправильные ;)
А какой правильный?
В passwd есть структура из нескольких полей, поэтому искать что-то grep'ом неправильно.
Либо резать на поля и искать (awk), либо взять готовую утилиту id
if id -u "$user" 2>&1 > /dev/null
then

, но это уже не поиск в passwd, а нечто большее.
Зависит от того, что хотели получить.
НЛО прилетело и опубликовало эту надпись здесь
А это уже зависит от того, что именно нужно найти. ;)
Если строку в файле — grep, если пользователя в системе — id. Авторизация через passwd — не единственный способ.

Можно ещё getent passwd, оно притаскивает из всего сконфигурированного в /etc/nsswitch.conf

Нужно!
Хм. Видимо собирались выпилить, но не решились, посту уже 7 лет.
Мельком посмотрел в ченжлогах — ничего не нашёл тоже.
Debian Jessie. bash 4.3.30.
НЛО прилетело и опубликовало эту надпись здесь
Просим гуру bash-программирования рассказать о том, как они добрались до вершин мастерства, поделиться секретами, а от тех, кто только что написал свой первый скрипт, ждём впечатлений.

Не читайте про bash на хабре, читайте man, --help и книги!
НЛО прилетело и опубликовало эту надпись здесь
Для меня когда-то давно было открытием, что [ — это программа и лежит она обычно в /usr/bin.

Кстати, я думал что [ — это симлинк на 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
А теперь — открытие «второго порядка»: ни /usr/bin/[, ни /use/bin/test не используются в вышеприведённых скриптах.

Нужно либо задавать полное имя, либо использовать другой shell (например /bin/sh), где эти команды не устроены…
да, прямо в man [ написано

NOTE: your shell may have its own version of test and/or [, which usually supersedes the version described here. Please refer to your shell's documentation for details about the options it supports.


Очень рекомендую использовать https://github.com/koalaman/shellcheck для проверки шелл скриптов как часть CI, ловит как простые, так и сложные штуки, и помогает не забывать про корнер кейсы на разных ОС, офигенный тул.

Действительно классная штука, но всегда полагаться на её предупреждения не стоит, ибо она не достаточно проницательна, чтобы понять какое поведение тебе нужно или некоторые сложные случаи.
Кстати, в некоторых дистрибутивах она доступна в репе (например в Ubuntu: sudo apt install shellcheck).
Есть плагин для SublimeText: SublimeLinter-shellcheck

Стоит отметить, что часто со второго-третьего раза, перечитывая описания предупреждений в их вики, таки находится более правильный вариант, который успокаивает и автора скрипта и shellcheck :)


Ну и у shellcheck есть формат комментариев для сапреса ворнингов.


А, кстати, на шелл скрипты можно и тесты писать https://github.com/gojuno/mainframer (см папку test и travis.yml), выглядит примерно так:


image

> Уважаемые читатели! Просим гуру bash-программирования рассказать о том, как они добрались до вершин мастерства, поделиться секретами, а от тех, кто только что написал свой первый скрипт, ждём впечатлений.

Прочитал ABS Guide. Периодически использую его как справку. Всё.
Соглашусь. Advanced Bash-Scripting Guide и man с интернетами для сложных случаев.
В таком случае вам не составит труда скопировать переменную A в переменную B?

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 нужен. Хотя можно обойтись, но будет некрасиво…
Замечу, в программистских исходниках очень редко попадаются имена файлов с возвратами кареток ;)
Замечу, в программистских исходниках очень редко попадаются имена файлов с возвратами кареток ;)
Собственно это самая большая проблема с bash'ем: то, что «сходу» приходит на ум — как правило работает в 99% случаев. А потом в результате чьей-нибудь идиотской ошибки происходит "mkdir -p `cat some-crazy-file`" (что создаёт, вроде бы, безобидный пустой каталог и всё) и скрипты, «работавшие годами» вдруг взрываются…
В С, чтобы скопировать массив, тоже надо пару раз присесть ;)

А в чём проблема то? memcpy же.
И malloc. И рассчитать размер массива. И не забыть free. А если массив указателей, и данные тоже нужно скопировать?
Всё правильно. Но C, насколько мне известно, никогда не позиционирования как «простой в использовании язык для написания скриптов». Это — быстрый, но сложный в использовании — и относительно низкоуровневый язык программирования. Удивляться тому, что в нём сложно скопировать массив не приходится — это реально сложно сделать на уровне машинных кодов!

Но скриптовые языки, как бы, призваны сделать эти задачи простыми — пусть и ценой существенной потери производительности. Так вот bash тут — уникален: потери в производительности есть, ещё какие — а выигрыша в простоте использования нет!
И malloc. И рассчитать размер массива. И не забыть free

Это не только в C. Плюса, делфи, да много их таких.
А если массив указателей, и данные тоже нужно скопировать?

А это уже «отсебятинская» (по отношению к языку) структура данных. Опять таки, в большинстве языков их копирование вызовет аналогичные проблемы. Единственное, в C мало «не отсебятинских» структур.
И я о том. В обработке структур данных везде есть особенности. В bash тоже можно сделать поэлементное копирование, и все будет ясно и предсказуемо. Я бы так делал. А то, что есть способ копировать в 1 строчку, но зависимый от диалекта версии bash — это нехорошо.

Плюсовые массивы (aka векторы) копируются при присваивании.

Плюсовые массивы (aka векторы) — это не массивы, а контейнер собранный на их основе. С таким же успехом можно и в C написать функцию, которая будет копировать массив с выделением памяти под новый.
Это вы с чем-то перепутали. Плюсовые массивы — это та ещё кака. Их даже в функцию-то передать и вернуть из неё нельзя!

Но речь не о C.
С чего их нельзя в функцию передать и вернуть от туда?
Так C устроен, однако. При попытке передать в функцию — вы, на самом деле, передадите указатель на массив. Если попытаетесь вернуть — просто синтаксическая ошибка.
Я не совсем понимаю, о каких массивах речь. Если вы о C++ обертках вроде std::vector и подобных, то они без проблем туда-сюда тащатся по ссылкам или по значению (да и по указателю, если очень надо), если вы о чисто C'шных, то, на сколько известно, тягают их по указателю.
Стоит отметить, во многих языках сложные конструкции (в т.ч. и массивы) тягают по ссылке (суть указателю). Не понимаю, в чём проблема и в чём недостаток по сравнению с другими языками.
Не понимаю, в чём проблема и в чём недостаток по сравнению с другими языками.
Во временах жизни. Если вы внутри функции заведёте массив и вернете его «по ссылке» — то это добром не кончится.

А в bash и передачи по ссылке нету…
Во временах жизни. Если вы внутри функции заведёте массив и вернете его «по ссылке» — то это добром не кончится.

Ну, правильно. Потому что массив статический. Сделайте динамическим и возвращайте как хотите. При этом, говоря о плюсах, векторы нормально таскаются туда-сюда.
При этом в чём неудобство передачи по ссылки в функцию мне до сих пор не ясно.

А в bash и передачи по ссылке нету…

На счёт bash'а не знаю, но не редко скриптовые языки делают массивы не на стеке, а в куче. При этом в функции и из функций таскается указатель на них. Так что в них просто немного дополнительного синтаксического сахара, ничего принципиально отличного нет.

Типичные массивы в C — T[], не поддерживают копирование при присваивании просто так.
В C++ типичные массивы — std::vector, копируются при присваивании. Если вас не устраивает термин массив, давайте назовем списком.
Если писать на C++, то использовать стоит вещи из C++, имхо.

Но отличия C'шных T[] от C++ std::vector в том, что T[] — это конструкция языка, а std::vector — это конструкция библиотеки (и в принципе к языку имеет довольно опосредованное отношение). Никто не мешает обзавестись какой-нибудь 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?
Вряд ли. Обычно враппер к gcc вкручивают в кросс- компиляторные тулчейны, чтобы передавать компилятору дополнительные флаги или переменные окружения. А такая вот неявная зависимость кросс-компилятора от особенностей bash — это очень, очень плохо.
Ну тут как бы если всё сделать правильно и использовать "$@", то никаких зависимостей не будет. Проблема в том, что «интуитивный» и «естественный» код не работает.

Впрочем скажу одну вещь и в защиту bash'а сказать: пусть и с «некрасивым» синтаксисом, пусть и «странно» — но в нём эта задача делается. А вот в Windows мы не нашли ни одного встроенного инструмента, позволяющего это сделать. На cmd, ни powershell решить эту задачу не позволяют. Пришлось враппера на C писать в своё время…
На cmd, ни powershell решить эту задачу не позволяют.

Хм, заинтересовали. Если речь про эту, то вроде на cmd решаема при включении режима расширенной обработки команд (при желании, там не сложно и массивы поднять). Ну, или я задачку не совсем понял. Попробую на досуге.
Ну, или я задачку не совсем понял.
Задачку вы, я думаю, поняли — вы пробелемы не поняли.

Мне нужен «псевдо-gcc», который вызывает «настоящий» gcc с добавлением в командную строку пары аргументов. Всего-навсего. Чтобы можно было подсунуть его в произвольную систему сборки.

Кажется можно сделать просто gcc.cmd и всё? А вот и нет: что будет если система сборки вызывает какой-нибудь helper.cmd:
gcc %*
@copy /B %1.done+,, %1.done
Что будет если у вас в первой строке вызовется gcc.cmd? Правильно — touch уже не отработает.

Та же самая проблема с PowerShell: если система сборки использует не ShellExecute, а WinExec — то ваш скрипт работать не будет!

P.S. Конечно дальше выясняется, что и просто взять и написать программу на C тоже нельзя, но это — другая история.
О! Спасибо что напомнили ещё про один костыль.

А не проще в таком случае создать alias?
Нет, не проще. Алиасы работают только «внутри» bash'а, программы, вызванные из него их «не видят».
Так а вы внутри баша алиас и вызываете, в чем же проблема сделать алиас, который будет работать только внутри вашего скрипта на БАШЕ?
Зачем бы я таким странным способом вызывал gcc внутри моего собственно скрипта???

Нет, конечно — разумеется я этот скрипт хочу подсунуть в систему сборки вместо «настоящего» gcc.

А систем сборки на 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 "\\\\")"
\\
«Вдумчивое чтение документации», конечно, обьяснит в чём разница — но логичней всю эту коллекцию костылей не сделает…
Простите, серьезно? Вы ругаетесь на проблемы, которые были в V6 (1975 год) и V7(1979 год)?

Простейший пример: $(…) — это всего лишь замена на `…`?

Не очень удачный пример — конструкция $(...) появилась именно для того, чтобы быть нагляднее и решать конкретно этот случай, когда вам нужно вложить подстановку внутри подстановки, в остальном они равноправны.

Проблемы в вашей билд системе, 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? dash? bash? sh? kcsh? tcsh? zsh? Интерпретатор нужно указывать в начале скрипта и оно прекрасно будет запускаться, а ваше «расширение» ничего не делает и ничего не дает.
/bin/sh очевидно же!
Ага, очевидно человек для bash-скриптов тогда использует расширение .bash, при чем так как у bash от версии к версии были изменения еще и наверное расширения .bash2, bash3, bash4… Слабо верится.
Скрипты так то можно и на Ruby и на Lua если что писать. И интерпретатор там будет #!/usr/bin/env ruby и тд. если что.
Если расширение указывает на особенности того что внутри — почему бы и нет?
.sh — внутри скрипт на shell, скорее всего совместим с bash/ash/dash
.bash — внутри башизмы, может не запуститься на произвольном shell без напильника.

Именно это и имел ввиду. Добавлю еще, что скрипты могут быть и на PHP | python | ruby | etc...

От расширения вообще ничего не зависит.

Если не указан shabang с интерпретатором, запустится в том, откуда вызываете.

Упражнение на дом: что выведет последний скрипт если в переменной $mydir будет начинающееся с пробела имя существующей директории?

То же самое, если в $mydir пробел в середине. Выведет, но не то, что хотелось.

Именно. Убежден, что изучение командной оболочки *nix надо начинать с осознания таких вот драматических несоответствий в её дизайне. Типа:


— О, давай у нас оболочка сама будет шаблоны разворачивать!
— Круто!… Эй, подожди, у нас же имена файлов могут содержать почти любые символы, включая * и -?!!!
— Ну и что? В 99% случаев будет работать, да и ладно!


Чтобы потом не было мучительно больно за скрипт, выполнивший немного не то, что ты рассчитывал.

я понимаю что ко многим серверам нет доступа и нельзя поставить какой то сриптовый язык, но мне кажется стоит упомянуть что сложные задачи лучше получатся на чем нибудь другом не на баше
Ну сколько можно писать статьи о Bash и упорно лепить sh-совместимые команды?!
Bash гораздо функциональнее чем описанные здесь примеры.
Я уже не говорю о том, что вы пишите ужасный код, и некоторые привычки могут рано или поздно вылиться Вам боком, например использование [, вместо [[, или использование `$a', вместо `a' внутри $(( )). Конечно всё это рабочие варианты, если помнить о разных нюансах, но тогда не надо статью озаглавливать как «Bash-скрипты».
powershell реально мощнее bash, ждем полноценного прихода на linux/mac
НЛО прилетело и опубликовало эту надпись здесь
Вряд ли powershell придёт на линукс, потому что почти в любом дистрибутиве из коробки присутствует python. А он гораздо практичнее.
Если уж начали с азов про bash, то с самого начала надо объяснить почему так происходит

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 вместо пробела разрыв строки, это больше похож на какую-то особую конструкцию языка чем на три команды с параметрами.

Разрыв строки должен быть перед then, а не после, однако.

По коменту это очевидно, а по исходной статье — нет. Я бы придерживался the one true brace style:


if ["десять" \> "тыща"]; then
    echo true; 
fi

Этот semicolon слишком подозрительно выглядит чтобы его игнорировать, сразу понятно всё.

А зачем после echo true ставить semicolon?

В bash'е semicolon — просто синоним перевода строки, зачем вам ещё одна пустая строка там?
Как можно сравнить два массива чисел разной длины?

strcmp, strcmpi… Можно.
Зачем? Хотя бы чтобы был критерий для сортировки ;)

Для того, чтобы избавиться от этого предупреждения, заключим $val2 в двойные кавычки:

Это особенная, bash'евская магия! Если в $val2 пустая строка — в оператор сравнения не передается один из параметров. А если в $val2 строка с пробелами — передастся N параметров. Если пустая строка, но в кавычках — передастся пустая строка. А если в одинарных кавычках — переменные внутри кавычек не преобразуются в значения.

КМК очень, очень плохая идея — обрабатывать строки в bash. Разве что от безысходности. perl создан для этого ;).
Совершенно непонятно ЧТО сравнивается в строках, а потом статья говорит что можно еще и сортировать с помощью этого оператора.
А как строки сравниваются в других языках — вам понятно? Pascal, Pyhton, C? stroll? Толковый словарь Ожегова?

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

Лексикографический порядок не является единственным возможным вариантом. Сходу нагуглился как минимум Kleene–Brouwer order. Кроме того, статья позиционируется как обучающая и совершенно точно не должна оставлять таких вопросов (ссылки на википедию было бы более чем достаточно). Я радикально не согласен с тем что задачей статьи не является "обучить писать скрипты человека, который о программировании не знает ничего вообще", потому что для автоматизции простых задач совершенно не требуется знать что такое лексикографический порядок и что на самом деле "десять" > "тыща".

Тут есть некоторая проблема: судя по вашему тону вы считаете, что писать скрипты на bash'е — относительно легко и этому полезно обучать новичков.

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

А если вас необходимо писать скрипты по той или иной причине, то вы, скорее всего уже не один язык программирования знаете и рассказывать вам про лексикографический порядок — не нужно от слова «совсем»…

P.S. Если же ваша задача не «написать скрипт, который работает независимо от того, есть ли в имени файла, с которым он работает, возврат каретки или нет», а «написать скрипт, который, как правило, работает — если звёзды стоят правильно», то тут и вообще никакой серии статей не нужно: SODD вполне работает, если вас устраивает не вполне гарнтированный результат — зачем ещё и статьи какие-то?

Вот сейчас Вы правы, я считал что bash позиционируется как "простой" язык. Если в реальности bash-скриптинг — это действительно более ребусы, чем продуктивность, то да, лучше на том же питоне писать (лично я так и делаю). Но все-таки, мне всегда казалось что я что-то упускаю… Ведь именно bash является стандартной оболочкой всех современных линуксов. Создаётся впечатление, что это стандарт отрасли и вообще говоря у него большие шансы стать первым языком у юзера хотя бы потому что ему поневоле приходится писать apt-get и dpkg -i.


Если необходимо писать и есть конкретная задача, то решить её при помощи Гугла или даже man — никакого труда не составит. А статья нужна чтобы узнать как правильно, надёжно и без хаков писать рутинные вещи типа if. И перестать тратить время на выяснение этого каждый раз. Хотя, про то что это именно та статья, которая будет находится по запросам в Гугл, я не подумал. Но судя по количеству неточностей в статье, которые раскрыты коментами, не уверен что это хорошо.

как правильно, надёжно и без хаков писать рутинные вещи типа if


man же. Кроме if, есть и другие конструкции.

Ну, давайте не будем писать обучающие статьи вообще. И переводить man тоже не нужно, каждый кто общается с консолью обязан знать английский. И nano выпилить из всех дистрибутивов, только sed и vim, пусть как хотят так и колупаются, nano не тру. Повысим порог вхождения до небес, nobody needs lamers.

Синтаксис языковых конструкций лучше смотреть в первоисточнике. А в обучающей статье лучше бы рассказать, зачем вообще (не)нужно писать скрипты на bash. С реальными use case.
Я вот знаю только одно применения для bash'а: на нём необходимо писать скрипты для сборки всяких rpm'ов и ebuild'ов. По историческим причинам.

Также полезно его знать если вы используете 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 и прочие штуки, но давайте посмотрим правде в глаза: это корявые инструменты. Очень корявые. То, что мы научились с ними как-то жить — этой истины, увы, не отменяет…
Идеальных инструментов не бывает. Вы знаете о недостатках bash — это не делает его плохим. Просто не надо делать на нем то, к чему он не приспособлен.
Да, пример с find мне кажется натянутым. :)
Просто не надо делать на нем то, к чему он не приспособлен.
Он «не приспособлен» работать со списками файлов, среди которых может встретится «нечто странное» (пробелы, хотя бы). Что, в общем, делает его мало пригодным для чего-либо вообще: в современном мире инструмент, который может, внезапно, сломаться, если у вас в каталоге обнаружится файл с «неправильным» именем — просто слишком опасен, чтобы рекомендовать его использовать где-бы-то-ни-было… Слишком велик риск…
А на каком другом языке такие нетривиальные строки будут выглядеть лаконично? Здаётся мне, что на любом будет выглядеть замысловато.
Вот вам Python:

#!/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, конечно, длиннее, но, в общем, вполне понятен и не напоминает ребус…
Вот только прикол в том, что символ zero не очень текстовый, а баш изначально предполагает работу с текстом, поэтому вы нашли одну проблему, когда сложно работать с символом zero и делаете из этого неудобство?

При этом find/xargs отлично работают с zero
При этом можно найти кучу других вариантов что можно с этим сделать.
Вот только прикол в том, что символ zero не очень текстовый, а баш изначально предполагает работу с текстом, поэтому вы нашли одну проблему, когда сложно работать с символом zero и делаете из этого неудобство?
Неудобство из этого сделал не я, а создатели Unix, извините.

Они, с одной стороны, разработали систему, в которой в именах файлов могут встречаться любые символы, за исключением символа с кодом ноль (на слеши есть определённые ограничения, да, но они всё равно там могут быть, потому разделителем списка с именами файлов они быть не могут) — отсюда, собственно, все эти xargs -0, sort -z и прочее.

А с другой — они же создали инструмент, который плохо себя ведёт не только с этим символом, но и с возвратами кареток, пробелами и прочим.

При этом find/xargs отлично работают с zero
Работают, да. Но «отлично» — я бы не сказал. Это всё равно не умолчание.

При этом можно найти кучу других вариантов что можно с этим сделать.
Что? Я вижу только два варианта:
  1. Увеличить список запрещённых символов в именах файлов. Если исключить оттуда, скажем, возврат каретки — станет уже лучше. А если ещё и потребовать, чтобы имя файла было валидной UTF8-последовательностью — станет совсем хорошо.
  2. Отказаться от bash и использовать языки, для которых обработка таких файлов не представляет сложности.
    Вариантов много: python, go, и масса других языков.

Так как сообщество в варианте #1 явно не заинтересовано (про это говорят уже десять лет, есть подробные предложения — однако никаких подвижек нет: никакого простого способа запретить создание таких файлов Linux по-прежнему не предлагает), то остаётся только вариант #2.

Или у вас есть какие-нибудь другие варианты? Где ваша «куча»?

P.S. Я про это уже писал: проблема bash не в том, что он чего-то не позволяет сделать. Проблема bash в том, очевидные подходы небезопасны, а безопасные подходы — неочевидны. Да, я понимаю, что это не злой умысел, а призрак Черномырдина… но от этого не легче. Лучше пользоваться средами, где очевидные — подходы безопасны, а безопасные — очевидны. Да, подводные камни есть везде, но bash… это просто клиника.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий