Сценарии командной строки — это наборы тех же самых команд, которые можно вводить с клавиатуры, собранные в файлы и объединённые некоей общей целью. При этом результаты работы команд могут представлять либо самостоятельную ценность, либо служить входными данными для других команд. Сценарии — это мощный способ автоматизации часто выполняемых действий.
Итак, если говорить о командной строке, она позволяет выполнить несколько команд за один раз, введя их через точку с запятой:
pwd ; whoami
На самом деле, если вы опробовали это в своём терминале, ваш первый bash-скрипт, в котором задействованы две команды, уже написан. Работает он так. Сначала команда
pwd
выводит на экран сведения о текущей рабочей директории, потом команда whoami
показывает данные о пользователе, под которым вы вошли в систему.Используя подобный подход, вы можете совмещать сколько угодно команд в одной строке, ограничение — лишь в максимальном количестве аргументов, которое можно передать программе. Определить это ограничение можно с помощью такой команды:
getconf ARG_MAX
Командная строка — отличный инструмент, но команды в неё приходится вводить каждый раз, когда в них возникает необходимость. Что если записать набор команд в файл и просто вызывать этот файл для их выполнения? Собственно говоря, тот файл, о котором мы говорим, и называется сценарием командной строки.
Как устроены bash-скрипты
Создайте пустой файл с использованием команды
touch
. В его первой строке нужно указать, какую именно оболочку мы собираемся использовать. Нас интересует bash
, поэтому первая строка файла будет такой:#!/bin/bash
В других строках этого файла символ решётки используется для обозначения комментариев, которые оболочка не обрабатывает. Однако, первая строка — это особый случай, здесь решётка, за которой следует восклицательный знак (эту последовательность называют шебанг) и путь к
bash
, указывают системе на то, что сценарий создан именно для bash
.Команды оболочки отделяются знаком перевода строки, комментарии выделяют знаком решётки. Вот как это выглядит:
#!/bin/bash
# This is a comment
pwd
whoami
Тут, так же, как и в командной строке, можно записывать команды в одной строке, разделяя точкой с запятой. Однако, если писать команды на разных строках, файл легче читать. В любом случае оболочка их обработает.
Установка разрешений для файла сценария
Сохраните файл, дав ему имя
myscript
, и работа по созданию bash-скрипта почти закончена. Сейчас осталось лишь сделать этот файл исполняемым, иначе, попытавшись его запустить, вы столкнётесь с ошибкой Permission denied
.Попытка запуска файла сценария с неправильно настроенными разрешениями
Сделаем файл исполняемым:
chmod +x ./myscript
Теперь попытаемся его выполнить:
./myscript
После настройки разрешений всё работает как надо.
Успешный запуск bash-скрипта
Вывод сообщений
Для вывода текста в консоль Linux применяется команда
echo
. Воспользуемся знанием этого факта и отредактируем наш скрипт, добавив пояснения к данным, которые выводят уже имеющиеся в нём команды:#!/bin/bash
# our comment is here
echo "The current directory is:"
pwd
echo "The user logged in is:"
whoami
Вот что получится после запуска обновлённого скрипта.
Вывод сообщений из скрипта
Теперь мы можем выводить поясняющие надписи, используя команду
echo
. Если вы не знаете, как отредактировать файл, пользуясь средствами Linux, или раньше не встречались с командой echo
, взгляните на этот материал.Использование переменных
Переменные позволяют хранить в файле сценария информацию, например — результаты работы команд для использования их другими командами.
Нет ничего плохого в исполнении отдельных команд без хранения результатов их работы, но возможности такого подхода весьма ограничены.
Существуют два типа переменных, которые можно использовать в bash-скриптах:
- Переменные среды
- Пользовательские переменные
Переменные среды
Иногда в командах оболочки нужно работать с некими системными данными. Вот, например, как вывести домашнюю директорию текущего пользователя:
#!/bin/bash
# display user home
echo "Home for the current user is: $HOME"
Обратите внимание на то, что мы можем использовать системную переменную
$HOME
в двойных кавычках, это не помешает системе её распознать. Вот что получится, если выполнить вышеприведённый сценарий.Использование переменной среды в сценарии
А что если надо вывести на экран значок доллара? Попробуем так:
echo "I have $1 in my pocket"
Система обнаружит знак доллара в строке, ограниченной кавычками, и решит, что мы сослались на переменную. Скрипт попытается вывести на экран значение неопределённой переменной
$1
. Это не то, что нам нужно. Что делать?В подобной ситуации поможет использование управляющего символа, обратной косой черты, перед знаком доллара:
echo "I have \$1 in my pocket"
Теперь сценарий выведет именно то, что ожидается.
Использование управляющей последовательности для вывода знака доллара
Пользовательские переменные
В дополнение к переменным среды, bash-скрипты позволяют задавать и использовать в сценарии собственные переменные. Подобные переменные хранят значение до тех пор, пока не завершится выполнение сценария.
Как и в случае с системными переменными, к пользовательским переменным можно обращаться, используя знак доллара:
TNW-CUS-FMP — промо-код на 10% скидку на наши услуги, доступен для активации в течение 7 дней
#!/bin/bash
# testing variables
grade=5
person="Adam"
echo "$person is a good boy, he is in grade $grade"
Вот что получится после запуска такого сценария.
Пользовательские переменные в сценарии
Подстановка команд
Одна из самых полезных возможностей bash-скриптов — это возможность извлекать информацию из вывода команд и назначать её переменным, что позволяет использовать эту информацию где угодно в файле сценария.
Сделать это можно двумя способами.
- С помощью значка обратного апострофа «`»
- С помощью конструкции
$()
Используя первый подход, проследите за тем, чтобы вместо обратного апострофа не ввести одиночную кавычку. Команду нужно заключить в два таких значка:
mydir=`pwd`
При втором подходе то же самое записывают так:
mydir=$(pwd)
А скрипт, в итоге, может выглядеть так:
#!/bin/bash
mydir=$(pwd)
echo $mydir
В ходе его работы вывод команды
pwd
будет сохранён в переменной mydir
, содержимое которой, с помощью команды echo
, попадёт в консоль.Скрипт, сохраняющий результаты работы команды в переменной
Математические операции
Для выполнения математических операций в файле скрипта можно использовать конструкцию вида
$((a+b))
:#!/bin/bash
var1=$(( 5 + 5 ))
echo $var1
var2=$(( $var1 * 2 ))
echo $var2
Математические операции в сценарии
Управляющая конструкция if-then
В некоторых сценариях требуется управлять потоком исполнения команд. Например, если некое значение больше пяти, нужно выполнить одно действие, в противном случае — другое. Подобное применимо в очень многих ситуациях, и здесь нам поможет управляющая конструкция
if-then
. В наиболее простом виде она выглядит так:if команда
then
команды
fi
А вот рабочий пример:
#!/bin/bash
if pwd
then
echo "It works"
fi
В данном случае, если выполнение команды
pwd
завершится успешно, в консоль будет выведен текст «it works».Воспользуемся имеющимися у нас знаниями и напишем более сложный сценарий. Скажем, надо найти некоего пользователя в
/etc/passwd
, и если найти его удалось, сообщить о том, что он существует.#!/bin/bash
user=likegeeks
if grep $user /etc/passwd
then
echo "The user $user Exists"
fi
Вот что получается после запуска этого скрипта.
Поиск пользователя
Здесь мы воспользовались командой
grep
для поиска пользователя в файле /etc/passwd
. Если команда grep
вам незнакома, её описание можно найти здесь.В этом примере, если пользователь найден, скрипт выведет соответствующее сообщение. А если найти пользователя не удалось? В данном случае скрипт просто завершит выполнение, ничего нам не сообщив. Хотелось бы, чтобы он сказал нам и об этом, поэтому усовершенствуем код.
Управляющая конструкция if-then-else
Для того, чтобы программа смогла сообщить и о результатах успешного поиска, и о неудаче, воспользуемся конструкцией
if-then-else
. Вот как она устроена:if команда
then
команды
else
команды
fi
Если первая команда возвратит ноль, что означает её успешное выполнение, условие окажется истинным и выполнение не пойдёт по ветке
else
. В противном случае, если будет возвращено что-то, отличающееся от нуля, что будет означать неудачу, или ложный результат, будут выполнены команды, расположенные после else
.Напишем такой скрипт:
#!/bin/bash
user=anotherUser
if grep $user /etc/passwd
then
echo "The user $user Exists"
else
echo "The user $user doesn’t exist"
fi
Его исполнение пошло по ветке
else
.Запуск скрипта с конструкцией if-then-else
Ну что же, продолжаем двигаться дальше и зададимся вопросом о более сложных условиях. Что если надо проверить не одно условие, а несколько? Например, если нужный пользователь найден, надо вывести одно сообщение, если выполняется ещё какое-то условие — ещё одно сообщение, и так далее. В подобной ситуации нам помогут вложенные условия. Выглядит это так:
if команда1
then
команды
elif команда2
then
команды
fi
Если первая команда вернёт ноль, что говорит о её успешном выполнении, выполнятся команды в первом блоке
then
, иначе, если первое условие окажется ложным, и если вторая команда вернёт ноль, выполнится второй блок кода.#!/bin/bash
user=anotherUser
if grep $user /etc/passwd
then
echo "The user $user Exists"
elif ls /home
then
echo "The user doesn’t exist but anyway there is a directory under /home"
fi
В подобном скрипте можно, например, создавать нового пользователя с помощью команды
useradd
, если поиск не дал результатов, или делать ещё что-нибудь полезное.Сравнение чисел
В скриптах можно сравнивать числовые значения. Ниже приведён список соответствующих команд.
n1 -eq n2
Возвращает истинное значение, еслиn1
равноn2
.
n1 -ge n2
Возвращает истинное значение, еслиn1
больше или равноn2
.
n1 -gt n2
Возвращает истинное значение, еслиn1
большеn2
.
n1 -le n2
Возвращает истинное значение, еслиn1
меньше или равноn2
.
n1 -lt n2
Возвращает истинное значение, если n1 меньшеn2
.
n1 -ne n2
Возвращает истинное значение, еслиn1
не равноn2
.
В качестве примера опробуем один из операторов сравнения. Обратите внимание на то, что выражение заключено в квадратные скобки.
#!/bin/bash
val1=6
if [ $val1 -gt 5 ]
then
echo "The test value $val1 is greater than 5"
else
echo "The test value $val1 is not greater than 5"
fi
Вот что выведет эта команда.
Сравнение чисел в скриптах
Значение переменной
val1
больше чем 5, в итоге выполняется ветвь then
оператора сравнения и в консоль выводится соответствующее сообщение.Сравнение строк
В сценариях можно сравнивать и строковые значения. Операторы сравнения выглядят довольно просто, однако у операций сравнения строк есть определённые особенности, которых мы коснёмся ниже. Вот список операторов.
str1 = str2
Проверяет строки на равенство, возвращает истину, если строки идентичны.
str1 != str2
Возвращает истину, если строки не идентичны.
str1 < str2
Возвращает истину, еслиstr1
меньше, чемstr2
.
str1 > str2
Возвращает истину, еслиstr1
больше, чемstr2
.
-n str1
Возвращает истину, если длинаstr1
больше нуля.
-z str1
Возвращает истину, если длинаstr1
равна нулю.
Вот пример сравнения строк в сценарии:
#!/bin/bash
user ="likegeeks"
if [$user = $USER]
then
echo "The user $user is the current logged in user"
fi
В результате выполнения скрипта получим следующее.
Сравнение строк в скриптах
Вот одна особенность сравнения строк, о которой стоит упомянуть. А именно, операторы «>» и «<» необходимо экранировать с помощью обратной косой черты, иначе скрипт будет работать неправильно, хотя сообщений об ошибках и не появится. Скрипт интерпретирует знак «>» как команду перенаправления вывода.
Вот как работа с этими операторами выглядит в коде:
#!/bin/bash
val1=text
val2="another text"
if [ $val1 \> $val2 ]
then
echo "$val1 is greater than $val2"
else
echo "$val1 is less than $val2"
fi
Вот результаты работы скрипта.
Сравнение строк, выведенное предупреждение
Обратите внимание на то, что скрипт, хотя и выполняется, выдаёт предупреждение:
./myscript: line 5: [: too many arguments
Для того, чтобы избавиться от этого предупреждения, заключим
$val2
в двойные кавычки:#!/bin/bash
val1=text
val2="another text"
if [ $val1 \> "$val2" ]
then
echo "$val1 is greater than $val2"
else
echo "$val1 is less than $val2"
fi
Теперь всё работает как надо.
Сравнение строк
Ещё одна особенность операторов «>» и «<» заключается в том, как они работают с символами в верхнем и нижнем регистрах. Для того, чтобы понять эту особенность, подготовим текстовый файл с таким содержимым:
Likegeeks
likegeeks
Сохраним его, дав имя
myfile
, после чего выполним в терминале такую команду:sort myfile
Она отсортирует строки из файла так:
likegeeks
Likegeeks
Команда
sort
, по умолчанию, сортирует строки по возрастанию, то есть строчная буква в нашем примере меньше прописной. Теперь подготовим скрипт, который будет сравнивать те же строки:#!/bin/bash
val1=Likegeeks
val2=likegeeks
if [ $val1 \> $val2 ]
then
echo "$val1 is greater than $val2"
else
echo "$val1 is less than $val2"
fi
Если его запустить, окажется, что всё наоборот — строчная буква теперь больше прописной.
Команда sort и сравнение строк в файле сценария
В командах сравнения прописные буквы меньше строчных. Сравнение строк здесь выполняется путём сравнения ASCII-кодов символов, порядок сортировки, таким образом, зависит от кодов символов.
Команда
sort
, в свою очередь, использует порядок сортировки, заданный в настройках системного языка.Проверки файлов
Пожалуй, нижеприведённые команды используются в bash-скриптах чаще всего. Они позволяют проверять различные условия, касающиеся файлов. Вот список этих команд.
-d file
Проверяет, существует ли файл, и является ли он директорией.
-e file
Проверяет, существует ли файл.
-f file
Проверяет, существует ли файл, и является ли он файлом.
-r file
Проверяет, существует ли файл, и доступен ли он для чтения.
-s file П
роверяет, существует ли файл, и не является ли он пустым.
-w file
Проверяет, существует ли файл, и доступен ли он для записи.
-x file
Проверяет, существует ли файл, и является ли он исполняемым.
file1 -nt file2
Проверяет, новее лиfile1
, чемfile2
.
file1 -ot file2
Проверяет, старше лиfile1
, чемfile2
.
-O file
Проверяет, существует ли файл, и является ли его владельцем текущий пользователь.
-G file
Проверяет, существует ли файл, и соответствует ли его идентификатор группы идентификатору группы текущего пользователя.
Эти команды, как впрочем, и многие другие рассмотренные сегодня, несложно запомнить. Их имена, являясь сокращениями от различных слов, прямо указывают на выполняемые ими проверки.
Опробуем одну из команд на практике:
#!/bin/bash
mydir=/home/likegeeks
if [ -d $mydir ]
then
echo "The $mydir directory exists"
cd $ mydir
ls
else
echo "The $mydir directory does not exist"
fi
Этот скрипт, для существующей директории, выведет её содержимое.
Вывод содержимого директории
Полагаем, с остальными командами вы сможете поэкспериментировать самостоятельно, все они применяются по тому же принципу.
Итоги
Сегодня мы рассказали о том, как приступить к написанию bash-скриптов и рассмотрели некоторые базовые вещи. На самом деле, тема bash-программирования огромна. Эта статья является переводом первой части большой серии из 11 материалов. Если вы хотите продолжения прямо сейчас — вот список оригиналов этих материалов. Для удобства сюда включён и тот, перевод которого вы только что прочли.
- Bash Script Step By Step — здесь речь идёт о том, как начать создание bash-скриптов, рассмотрено использование переменных, описаны условные конструкции, вычисления, сравнения чисел, строк, выяснение сведений о файлах.
- Bash Scripting Part 2, Bash the awesome — тут раскрываются особенности работы с циклами for и while.
- Bash Scripting Part 3, Parameters & options — этот материал посвящён параметрам командной строки и ключам, которые можно передавать скриптам, работе с данными, которые вводит пользователь, и которые можно читать из файлов.
- Bash Scripting Part 4, Input & Output — здесь речь идёт о дескрипторах файлов и о работе с ними, о потоках ввода, вывода, ошибок, о перенаправлении вывода.
- Bash Scripting Part 5, Sighals & Jobs — этот материал посвящён сигналам Linux, их обработке в скриптах, запуску сценариев по расписанию.
- Bash Scripting Part 6, Functions — тут можно узнать о создании и использовании функций в скриптах, о разработке библиотек.
- Bash Scripting Part 7, Using sed — эта статья посвящена работе с потоковым текстовым редактором sed.
- Bash Scripting Part 8, Using awk — данный материал посвящён программированию на языке обработки данных awk.
- Bash Scripting Part 9, Regular Expressions — тут можно почитать об использовании регулярных выражений в bash-скриптах.
- Bash Scripting Part 10, Practical Examples — здесь приведены приёмы работы с сообщениями, которые можно отправлять пользователям, а так же методика мониторинга диска.
- Bash Scripting Part 11, Expect Command — этот материал посвящён средству Expect, с помощью которого можно автоматизировать взаимодействие с интерактивными утилитами. В частности, здесь идёт речь об expect-скриптах и об их взаимодействии с bash-скриптами и другими программами.
Полагаем, одно из ценных свойств этой серии статей заключается в том, что она, начинаясь с самого простого, подходящего для пользователей любого уровня, постепенно ведёт к довольно серьёзным темам, давая шанс всем желающим продвинуться в деле создания сценариев командной строки Linux.
Уважаемые читатели! Просим гуру bash-программирования рассказать о том, как они добрались до вершин мастерства, поделиться секретами, а от тех, кто только что написал свой первый скрипт, ждём впечатлений.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (108)
andreymal
03.04.2017 15:15+3#!/bin/bash
Позанудствую: во FreeBSD баш пихают в
/usr/local/bin/bash
, поэтому такой скрипт там не запустится. Так что теперь я пишу#!/usr/bin/env bash
(правда, где-то читал, что это тоже где-то может не работать, но я уже забыл где)POS_troi
03.04.2017 18:28по хорошему симлинк делается, если сам не сделался то сделать и все проблемы исчезнут :)
andreymal
03.04.2017 19:34+1Думается мне, лезть в системные файлы, которые для лазания не предназначены, не очень хорошо
Proxaber
03.04.2017 21:43-1В Linux все системные файлы для того и нужны — чтоб было что ковырять)
Erelecano
03.04.2017 22:42-2Вас обманули. В системах на базе ядра Linux системные файлы нужны, что бы все нормально работало, именно по этой причине GNU/Linux лидирует на серверном рынке(а в TOP500 просто занимает 99.9% мест). А детишкам, которые руками лезут туда, куда писать должен пакетный менеджер место на своих локалхостах. Мамкиным какирам мамкино какерство!
groaner
03.04.2017 23:22+1Ага, именно поэтому видимо большинство системных настроек хранится в обычных текстовых файлах — очень уж удобно программам с ними работать. А главное, как производительно!
Erelecano
03.04.2017 21:19+1Делать симлинки в /bin это по плохому. Правильно, как вам уже выше написали #!/usr/bin/env bash, аналогично и #!/usr/bin/env python, #!/usr/bin/env perl и так далее.
Twindo
03.04.2017 15:54+1Было бы интересно почитать про развертывание приложений при помощи bash-скриптов
A1estro
03.04.2017 15:54+2Вместо
$((1+1))
намного читабельнее использовать$[1+1]
if grep $user /etc/passwd
надо либо -q, либо вывод перенаправить.iig
03.04.2017 17:09if grep $user /etc/passwd
надо либо -q, либо вывод перенаправить
Все 3 способа неправильные ;)myrslok
04.04.2017 17:35А какой правильный?
iig
04.04.2017 17:43В passwd есть структура из нескольких полей, поэтому искать что-то grep'ом неправильно.
Либо резать на поля и искать (awk), либо взять готовую утилиту id
if id -u "$user" 2>&1 > /dev/null then
, но это уже не поиск в passwd, а нечто большее.
Зависит от того, что хотели получить.Hissing_Bridge
06.04.2017 16:17искать что-то grep'ом неправильно.
С чего это? Как раз правильно, просто нужно воспользвоаться регуляркой
grep -qE "^$user:"
iig
06.04.2017 16:38+2А это уже зависит от того, что именно нужно найти. ;)
Если строку в файле — grep, если пользователя в системе — id. Авторизация через passwd — не единственный способ.grossws
06.04.2017 17:24Можно ещё
getent passwd
, оно притаскивает из всего сконфигурированного в/etc/nsswitch.conf
am-amotion-city
03.04.2017 16:07Нет ничего плохого в исполнении отдельных команд без хранения результатов их работы, но возможности такого подхода весьма ограничены.
И вот так мы походя оболгали весь unix-way (подсказка: пайп).
Ordinatus
03.04.2017 17:44+5Просим гуру bash-программирования рассказать о том, как они добрались до вершин мастерства, поделиться секретами, а от тех, кто только что написал свой первый скрипт, ждём впечатлений.
Не читайте про bash на хабре, читайте man, --help и книги!
lorc
03.04.2017 19:02Для меня когда-то давно было открытием, что [ — это программа и лежит она обычно в /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
khim
03.04.2017 19:43А теперь — открытие «второго порядка»: ни /usr/bin/[, ни /use/bin/test не используются в вышеприведённых скриптах.
Нужно либо задавать полное имя, либо использовать другой shell (например /bin/sh), где эти команды не устроены…lorc
03.04.2017 19:46да, прямо в 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.
Artem_zin
03.04.2017 19:30+4Очень рекомендую использовать https://github.com/koalaman/shellcheck для проверки шелл скриптов как часть CI, ловит как простые, так и сложные штуки, и помогает не забывать про корнер кейсы на разных ОС, офигенный тул.
vmspike
03.04.2017 20:49+1Действительно классная штука, но всегда полагаться на её предупреждения не стоит, ибо она не достаточно проницательна, чтобы понять какое поведение тебе нужно или некоторые сложные случаи.
Кстати, в некоторых дистрибутивах она доступна в репе (например в Ubuntu:sudo apt install shellcheck
).
Есть плагин для SublimeText: SublimeLinter-shellcheckArtem_zin
04.04.2017 03:39Стоит отметить, что часто со второго-третьего раза, перечитывая описания предупреждений в их вики, таки находится более правильный вариант, который успокаивает и автора скрипта и shellcheck :)
Ну и у shellcheck есть формат комментариев для сапреса ворнингов.
А, кстати, на шелл скрипты можно и тесты писать https://github.com/gojuno/mainframer (см папку
test
иtravis.yml
), выглядит примерно так:
overmind88
03.04.2017 19:48> Уважаемые читатели! Просим гуру bash-программирования рассказать о том, как они добрались до вершин мастерства, поделиться секретами, а от тех, кто только что написал свой первый скрипт, ждём впечатлений.
Прочитал ABS Guide. Периодически использую его как справку. Всё.AVX
03.04.2017 23:05Соглашусь. Advanced Bash-Scripting Guide и man с интернетами для сложных случаев.
khim
04.04.2017 19:25В таком случае вам не составит труда скопировать переменную A в переменную B?
P.S. Сам факт, что в bash'е эта задача весьма нетривиальна вызывает лёгкую грусть, конечно…iig
04.04.2017 22:28В чем нетривиальность?
khim
05.04.2017 00:03+1
Копируйте.declare A=([a]='x' [b]='y')
P.S. Эта проблема в bash4 появилась. В bash2 было так:
Просто, логично, понятно. А теперь, в bash4, чего делать? Есть несколько решений, но красивого я не знаю… что грустно: что это за язык такой, в котором переменную скопировать — проблема?$ declare -a A=('a' 'b c' 'd e') $ B=("${A[@]}") $ declare -p B declare -a B='([0]="a" [1]="b c" [2]="d e")'
iig
05.04.2017 09:14В С, чтобы скопировать массив, тоже надо пару раз присесть ;)
В bash операции со строками имеют особенности, а массивов, по возможности, лучше избегать. Простл запомнить это. Если нужны какие-то структуры данных — лучше взять python.khim
05.04.2017 16:17В С, чтобы скопировать массив, тоже надо пару раз присесть ;)
То что массивы в C сделаны неудобно и криво — это всем известно. И оправданием для bash являться никак не может.
А массивы в bash нужны. Как без них параметры командной строки обрабатывать?iig
05.04.2017 16:29while [ -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
Где-то так. Например.khim
05.04.2017 19:28Эты вы разобрали параметры, которые вам пришли обработали. А я про те, которые от вас уйти должны.
Напишите, скажем, скрипт с названием gcc, например, который получает аргументы, находит среди них -c, и, если получается, то вызывает gcc.real дважды: один раз «как есть», а второй — заменяя в командной в имени файла после -o расширение с .o на .ii и вместо -c вставляя -E.
С использованием массивов это делается. Без них — будут проблемы если имена файлов или каталогов будут с пробелами, возвратами кареток, etc.iig
05.04.2017 20:00Да, это тот случай, когда массив в bash нужен. Хотя можно обойтись, но будет некрасиво…
Замечу, в программистских исходниках очень редко попадаются имена файлов с возвратами кареток ;)khim
05.04.2017 20:07Замечу, в программистских исходниках очень редко попадаются имена файлов с возвратами кареток ;)
Собственно это самая большая проблема с bash'ем: то, что «сходу» приходит на ум — как правило работает в 99% случаев. А потом в результате чьей-нибудь идиотской ошибки происходит"mkdir -p `cat some-crazy-file`"
(что создаёт, вроде бы, безобидный пустой каталог и всё) и скрипты, «работавшие годами» вдруг взрываются…
neit_kas
08.04.2017 00:02В С, чтобы скопировать массив, тоже надо пару раз присесть ;)
А в чём проблема то? memcpy же.iig
08.04.2017 07:51И malloc. И рассчитать размер массива. И не забыть free. А если массив указателей, и данные тоже нужно скопировать?
khim
08.04.2017 12:50+1Всё правильно. Но C, насколько мне известно, никогда не позиционирования как «простой в использовании язык для написания скриптов». Это — быстрый, но сложный в использовании — и относительно низкоуровневый язык программирования. Удивляться тому, что в нём сложно скопировать массив не приходится — это реально сложно сделать на уровне машинных кодов!
Но скриптовые языки, как бы, призваны сделать эти задачи простыми — пусть и ценой существенной потери производительности. Так вот bash тут — уникален: потери в производительности есть, ещё какие — а выигрыша в простоте использования нет!
neit_kas
09.04.2017 05:28И malloc. И рассчитать размер массива. И не забыть free
Это не только в C. Плюса, делфи, да много их таких.
А если массив указателей, и данные тоже нужно скопировать?
А это уже «отсебятинская» (по отношению к языку) структура данных. Опять таки, в большинстве языков их копирование вызовет аналогичные проблемы. Единственное, в C мало «не отсебятинских» структур.iig
09.04.2017 07:45И я о том. В обработке структур данных везде есть особенности. В bash тоже можно сделать поэлементное копирование, и все будет ясно и предсказуемо. Я бы так делал. А то, что есть способ копировать в 1 строчку, но зависимый от
диалектаверсии bash — это нехорошо.
saboteur_kiev
08.04.2017 16:16в оригинальном баш нет ассоциативных массивов, следовательно нет и проблемы )
khim
08.04.2017 16:56Конкретно этой проблемы нет. Есть другие.
saboteur_kiev
08.04.2017 17:05Не очень понятно, что вы хотите сказать. Проблемы есть везде — и в программировании и в жизни.
Но функционал, который доступен в стандартном bash (posix), вполне интуитивен, и нетривиальные задачи, часто имеют более тривиальное решение. Но для этого нужно разбираться по существу.khim
08.04.2017 19:44+1Но функционал, который доступен в стандартном bash (posix), вполне интуитивен
В том-то и дело, что нет. Одну задачку мы уже обсуждали. Рассмотрим более простую: напишем скрипт, который мы, опять-таки, назовём gcc и хотим «подсунуть» в PATH. Мы хотим просто вызвать настоящий gcc добавив в командную строку опцию -V4.4.3 (предположим что система сборки у нас такая хитрая, что это в ней сделать сложно). Как нам нужно писать?
Нет, так не годится. Нужно вот так:/path/to/gcc -V4.4.3 $*
Это — один из первых «костылей», которые были добавлены в bash для того, чтобы простое, понятное, но не работающее решение превратить в хитрое и странное — но работающее!/path/to/gcc -V4.4.3 "$@"
В современном bash'е таких костылей — достаточно, и ассоциативные массивы — один из них. Они создают свои проблемы, но в старом bash'е жизнь, увы, не легче. Ибо этих костылей у вас нет и приходится извращаться. Там даже регулярных выражений нет у[[ … ]]
!alexyr
09.04.2017 09:58напишем скрипт, который мы, опять-таки, назовём gcc
А не проще в таком случае создать alias?iig
09.04.2017 11:26Вряд ли. Обычно враппер к gcc вкручивают в кросс- компиляторные тулчейны, чтобы передавать компилятору дополнительные флаги или переменные окружения. А такая вот неявная зависимость кросс-компилятора от особенностей bash — это очень, очень плохо.
khim
09.04.2017 13:23Ну тут как бы если всё сделать правильно и использовать
"$@"
, то никаких зависимостей не будет. Проблема в том, что «интуитивный» и «естественный» код не работает.
Впрочем скажу одну вещь и в защиту bash'а сказать: пусть и с «некрасивым» синтаксисом, пусть и «странно» — но в нём эта задача делается. А вот в Windows мы не нашли ни одного встроенного инструмента, позволяющего это сделать. На cmd, ни powershell решить эту задачу не позволяют. Пришлось враппера на C писать в своё время…neit_kas
09.04.2017 19:28На cmd, ни powershell решить эту задачу не позволяют.
Хм, заинтересовали. Если речь про эту, то вроде на cmd решаема при включении режима расширенной обработки команд (при желании, там не сложно и массивы поднять). Ну, или я задачку не совсем понял. Попробую на досуге.khim
10.04.2017 01:47Ну, или я задачку не совсем понял.
Задачку вы, я думаю, поняли — вы пробелемы не поняли.
Мне нужен «псевдо-gcc», который вызывает «настоящий» gcc с добавлением в командную строку пары аргументов. Всего-навсего. Чтобы можно было подсунуть его в произвольную систему сборки.
Кажется можно сделать просто gcc.cmd и всё? А вот и нет: что будет если система сборки вызывает какой-нибудь helper.cmd:
gcc %*
Что будет если у вас в первой строке вызовется gcc.cmd? Правильно —
@copy /B %1.done+,, %1.done
touch
уже не отработает.
Та же самая проблема с PowerShell: если система сборки использует не ShellExecute, а WinExec — то ваш скрипт работать не будет!
P.S. Конечно дальше выясняется, что и просто взять и написать программу на C тоже нельзя, но это — другая история.
khim
09.04.2017 13:19О! Спасибо что напомнили ещё про один костыль.
А не проще в таком случае создать alias?
Нет, не проще. Алиасы работают только «внутри» bash'а, программы, вызванные из него их «не видят».saboteur_kiev
10.04.2017 00:29Так а вы внутри баша алиас и вызываете, в чем же проблема сделать алиас, который будет работать только внутри вашего скрипта на БАШЕ?
khim
10.04.2017 01:34Зачем бы я таким странным способом вызывал gcc внутри моего собственно скрипта???
Нет, конечно — разумеется я этот скрипт хочу подсунуть в систему сборки вместо «настоящего» gcc.
А систем сборки на bash'е я, слава богу, давно не видел…
saboteur_kiev
10.04.2017 00:27Не понимаю, с чего вы взяли что нужно $*?
Из документации, как раз следует, что нужно $@, и это не костыль а как раз правильное использование.
https://www.gnu.org/software/bash/manual/bashref.html#Special-Parameters
Что же касается кавычек, то это и понятно — вы хотите, чтобы встреченные wildcards были раскрыты в вашем скрипте, или во время выполнения команды?
ВСЕ логично. Просто после вдумчивого чтения документации, интуиция работает значительно лучше.khim
10.04.2017 01:32+1Не понимаю, с чего вы взяли что нужно $*?
Потому что только так и можно было использовать переменные в 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 "\\\\")" \\
saboteur_kiev
11.04.2017 17:38Простите, серьезно? Вы ругаетесь на проблемы, которые были в V6 (1975 год) и V7(1979 год)?
Простейший пример: $(…) — это всего лишь замена на `…`?
Не очень удачный пример — конструкция $(...) появилась именно для того, чтобы быть нагляднее и решать конкретно этот случай, когда вам нужно вложить подстановку внутри подстановки, в остальном они равноправны.
Проблемы в вашей билд системе, IMHO заключается в том, что вы хотите своими баш скриптами запускать чужие баш скрипты, а про совместимость никто не думал.
Я могу ошибаться, но можно переходить на современные системы типа maven и всех проблем избежать. То есть IMHO зря наезжаете на баш.
Если брать именно os-related язык, то bash — гораздо лучше чем cmd/powershell и другие подобные языки.khim
11.04.2017 19:20+1Вы ругаетесь на проблемы, которые были в V6 (1975 год) и V7(1979 год)?
Я ругаюсь на язык, который содержит в себе большую коллекцию проблем и большую коллекцию костылей, предназначенных для обхода этих проблем. И в котором нужно «решать ребусы» при написании программ, так как простые решения — как правило не на 100% работоспособны.
Не очень удачный пример — конструкция $(...) появилась именно для того, чтобы быть нагляднее и решать конкретно этот случай, когда вам нужно вложить подстановку внутри подстановки, в остальном они равноправны.
Точно так же как $@ появилась чтобы решить проблему с использованием позиционных аргументов в кавычках, собственно. И также как[[ … ]]
появился чтобы решиьть прблемы с[ … ]
. Но при этом старые, «проблемные» механизмы никуда не делись, и, собственно, узнать о том, как нужно делать «правильно» зачастую неоткуда. Потому что в том же мануале описана разница между`…`
и$(…)
, но вот зачем эта разница нужна — ничего не сказано.
Проблемы в вашей билд системе, IMHO заключается в том, что вы хотите своими баш скриптами запускать чужие баш скрипты, а про совместимость никто не думал.
Проблема не билд-системе.
Я могу ошибаться, но можно переходить на современные системы типа maven и всех проблем избежать.
Интересная идея. У тебя спрашивают — как пользоватьтся вашим компилатором, а ты и отвечаешь «да никуда не годятся все ваши GNU Make, Ninja и прочие SCONS'ы, если вы хотите возпользоваться нашим чудо-компилятором — то maven и только maven, там всё будет работать».
Я боюсь начальство такое решение проблемы не одобрит, однако. И будет право.
Если брать именно os-related язык, то bash — гораздо лучше чем cmd/powershell и другие подобные языки.
Однако python (если его можно использовать) — ещё лучше!
vmspike
03.04.2017 20:59Shell Style Guide от Google
Тот самый Advanced Bash-Scripting Guide
Учебное пособие на eddnet.org
Тред на StackOverflow о скрытых фичах bash
Полезные одно-строчные скрипты sed
Ну и
man bash
периодически покуривать имея ввиду, что на удалённом хосте версия bash может отличаться и некоторые функции могут быть [не]доступны.
nsemikov
03.04.2017 21:44Всегда пишу расширение для скриптов. Может немного старомодно, но хотя бы нет путаницы: увидел myscript.sh и сразу понял какой интерперетатор используется.
Erelecano
03.04.2017 23:47Это не старомодно, это у вас привычки из винды. И какой же интерпретатор используется, если .sh? dash? bash? sh? kcsh? tcsh? zsh? Интерпретатор нужно указывать в начале скрипта и оно прекрасно будет запускаться, а ваше «расширение» ничего не делает и ничего не дает.
grieverrr
04.04.2017 01:03/bin/sh очевидно же!
Erelecano
04.04.2017 01:17Ага, очевидно человек для bash-скриптов тогда использует расширение .bash, при чем так как у bash от версии к версии были изменения еще и наверное расширения .bash2, bash3, bash4… Слабо верится.
ShadoWalkeR30
04.04.2017 13:22Скрипты так то можно и на Ruby и на Lua если что писать. И интерпретатор там будет #!/usr/bin/env ruby и тд. если что.
iig
04.04.2017 14:40Если расширение указывает на особенности того что внутри — почему бы и нет?
.sh — внутри скрипт на shell, скорее всего совместим с bash/ash/dash
.bash — внутри башизмы, может не запуститься на произвольном shell без напильника.nsemikov
04.04.2017 18:02Именно это и имел ввиду. Добавлю еще, что скрипты могут быть и на PHP | python | ruby | etc...
saboteur_kiev
07.04.2017 19:12От расширения вообще ничего не зависит.
Если не указан shabang с интерпретатором, запустится в том, откуда вызываете.
groaner
03.04.2017 22:51+1Упражнение на дом: что выведет последний скрипт если в переменной $mydir будет начинающееся с пробела имя существующей директории?
iig
03.04.2017 23:27То же самое, если в $mydir пробел в середине. Выведет, но не то, что хотелось.
groaner
04.04.2017 00:03+2Именно. Убежден, что изучение командной оболочки *nix надо начинать с осознания таких вот драматических несоответствий в её дизайне. Типа:
— О, давай у нас оболочка сама будет шаблоны разворачивать!
— Круто!… Эй, подожди, у нас же имена файлов могут содержать почти любые символы, включая * и -?!!!
— Ну и что? В 99% случаев будет работать, да и ладно!
Чтобы потом не было мучительно больно за скрипт, выполнивший немного не то, что ты рассчитывал.
Lelik13a
04.04.2017 05:12По нынешнем временам в баше предпочтительно использовать "[[ ]]" вместо "[ ]". На эту тему даже отдельный пункт есть в подводные камни Bash.
hagent
04.04.2017 07:24+1я понимаю что ко многим серверам нет доступа и нельзя поставить какой то сриптовый язык, но мне кажется стоит упомянуть что сложные задачи лучше получатся на чем нибудь другом не на баше
Alukardd
04.04.2017 14:27+2Ну сколько можно писать статьи о Bash и упорно лепить sh-совместимые команды?!
Bash гораздо функциональнее чем описанные здесь примеры.
Я уже не говорю о том, что вы пишите ужасный код, и некоторые привычки могут рано или поздно вылиться Вам боком, например использование [, вместо [[, или использование `$a', вместо `a' внутри $(( )). Конечно всё это рабочие варианты, если помнить о разных нюансах, но тогда не надо статью озаглавливать как «Bash-скрипты».
vd1
04.04.2017 14:52имхо лучшие материалы по bash это:
http://mywiki.wooledge.org/BashFAQ
http://mywiki.wooledge.org/BashGuide
http://wiki.bash-hackers.org/
Openmsk
04.04.2017 17:35-5powershell реально мощнее bash, ждем полноценного прихода на linux/mac
am-amotion-city
04.04.2017 18:13-1Разлагающихся стюардесс у нас у самих полно?. Сейчас не 1999 год, когда это могло бы быть хоть кому-то интересно.
Пусть powershell гниет там, где ему самое место: в винде.
fireSparrow
05.04.2017 09:30Вряд ли powershell придёт на линукс, потому что почти в любом дистрибутиве из коробки присутствует python. А он гораздо практичнее.
emanation
04.04.2017 17:53Если уж начали с азов про 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 команда не будет работать с двойными кавычками…
Himura
04.04.2017 18:43str1 < str2Возвращает истину, если str1меньше, чем str2.
А какая строка меньше "десять" или "тыща"? Совершенно непонятно ЧТО сравнивается в строках, а потом статья говорит что можно еще и сортировать с помощью этого оператора. WAT??? Сравниваются ASCII-коды символов?? Как можно сравнить два массива чисел разной длины? Может всё-таки по размеру сначала, а при равенстве размера какая-то еще логика? Вот этот вопрос не ясен...
Обратите внимание на то, что скрипт, хотя и выполняется, выдаёт предупреждение:
./myscript: line 5: [: too many arguments
Для того, чтобы избавиться от этого предупреждения, заключим $val2 в двойные кавычки:Здесь пропущен архи-важный ответ на вопрос "зачем?". Я не понимаю как bash интерпретировал выражение и ПОЧЕМУ кавычки эту интерпритацию меняют. Вот эти все сравнения строк для меня всегда были какой-то черной магией с десятком разных подходов и Ваша статья еще сильнее убеждает меня в том что так и есть.
Himura
04.04.2017 18:49Я даже проверить не смог после этой статьи… Не хватает хороших мануалов по 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:~#
iig
04.04.2017 19:09+1if ["десять" \> "тыща"] then; echo true; fi
if [ "десять" \> "тыща" ]; then echo true; fi
Найдите 3 отличия ;)Himura
04.04.2017 23:15ну да, я быстро сдался, спасибо. Надеюсь, запомню где там обязательные разрывы а где необязательные...
iig
04.04.2017 23:22+2Все обязательные ;)
[ — это команда с параметрами. Параметры разделяют пробелами.Himura
05.04.2017 09:10Вот теперь я вижу логику языка, спасибо. А когда после then вместо пробела разрыв строки, это больше похож на какую-то особую конструкцию языка чем на три команды с параметрами.
khim
05.04.2017 16:18Разрыв строки должен быть перед then, а не после, однако.
Himura
05.04.2017 16:40По коменту это очевидно, а по исходной статье — нет. Я бы придерживался the one true brace style:
if ["десять" \> "тыща"]; then echo true; fi
Этот semicolon слишком подозрительно выглядит чтобы его игнорировать, сразу понятно всё.
khim
05.04.2017 20:12+1А зачем после
echo true
ставить semicolon?
В bash'е semicolon — просто синоним перевода строки, зачем вам ещё одна пустая строка там?
iig
04.04.2017 19:01Как можно сравнить два массива чисел разной длины?
strcmp, strcmpi… Можно.
Зачем? Хотя бы чтобы был критерий для сортировки ;)
Для того, чтобы избавиться от этого предупреждения, заключим $val2 в двойные кавычки:
Это особенная, bash'евская магия! Если в $val2 пустая строка — в оператор сравнения не передается один из параметров. А если в $val2 строка с пробелами — передастся N параметров. Если пустая строка, но в кавычках — передастся пустая строка. А если в одинарных кавычках — переменные внутри кавычек не преобразуются в значения.
КМК очень, очень плохая идея — обрабатывать строки в bash. Разве что от безысходности. perl создан для этого ;).
khim
04.04.2017 19:21-1Совершенно непонятно ЧТО сравнивается в строках, а потом статья говорит что можно еще и сортировать с помощью этого оператора.
А как строки сравниваются в других языках — вам понятно? Pascal, Pyhton, C? stroll? Толковый словарь Ожегова?
Я не уверен что задачей статьи было обучить писать скрипты человека, который о программировании не знает ничего вообще — и гордится этим.Himura
04.04.2017 23:10Лексикографический порядок не является единственным возможным вариантом. Сходу нагуглился как минимум Kleene–Brouwer order. Кроме того, статья позиционируется как обучающая и совершенно точно не должна оставлять таких вопросов (ссылки на википедию было бы более чем достаточно). Я радикально не согласен с тем что задачей статьи не является "обучить писать скрипты человека, который о программировании не знает ничего вообще", потому что для автоматизции простых задач совершенно не требуется знать что такое лексикографический порядок и что на самом деле
"десять" > "тыща"
.khim
05.04.2017 00:42+1Тут есть некоторая проблема: судя по вашему тону вы считаете, что писать скрипты на bash'е — относительно легко и этому полезно обучать новичков.
К огромному сожалению оба посыла ложны: писать скрипты на bash сложно и, как правило, не нужно: есть много других языков, где решение простейших задач не превращается в ребус.
А если вас необходимо писать скрипты по той или иной причине, то вы, скорее всего уже не один язык программирования знаете и рассказывать вам про лексикографический порядок — не нужно от слова «совсем»…
P.S. Если же ваша задача не «написать скрипт, который работает независимо от того, есть ли в имени файла, с которым он работает, возврат каретки или нет», а «написать скрипт, который, как правило, работает — если звёзды стоят правильно», то тут и вообще никакой серии статей не нужно: SODD вполне работает, если вас устраивает не вполне гарнтированный результат — зачем ещё и статьи какие-то?Himura
05.04.2017 09:06Вот сейчас Вы правы, я считал что bash позиционируется как "простой" язык. Если в реальности bash-скриптинг — это действительно более ребусы, чем продуктивность, то да, лучше на том же питоне писать (лично я так и делаю). Но все-таки, мне всегда казалось что я что-то упускаю… Ведь именно bash является стандартной оболочкой всех современных линуксов. Создаётся впечатление, что это стандарт отрасли и вообще говоря у него большие шансы стать первым языком у юзера хотя бы потому что ему поневоле приходится писать
apt-get
иdpkg -i
.
Если необходимо писать и есть конкретная задача, то решить её при помощи Гугла или даже man — никакого труда не составит. А статья нужна чтобы узнать как правильно, надёжно и без хаков писать рутинные вещи типа if. И перестать тратить время на выяснение этого каждый раз. Хотя, про то что это именно та статья, которая будет находится по запросам в Гугл, я не подумал. Но судя по количеству неточностей в статье, которые раскрыты коментами, не уверен что это хорошо.
iig
05.04.2017 09:34как правильно, надёжно и без хаков писать рутинные вещи типа if
man же. Кроме if, есть и другие конструкции.Himura
05.04.2017 09:46+1Ну, давайте не будем писать обучающие статьи вообще. И переводить man тоже не нужно, каждый кто общается с консолью обязан знать английский. И nano выпилить из всех дистрибутивов, только sed и vim, пусть как хотят так и колупаются, nano не тру. Повысим порог вхождения до небес, nobody needs lamers.
iig
05.04.2017 10:19Синтаксис языковых конструкций лучше смотреть в первоисточнике. А в обучающей статье лучше бы рассказать, зачем вообще (не)нужно писать скрипты на bash. С реальными use case.
khim
06.04.2017 03:00Я вот знаю только одно применения для bash'а: на нём необходимо писать скрипты для сборки всяких rpm'ов и ebuild'ов. По историческим причинам.
Также полезно его знать если вы используете make и тому подобные вещи.
В обоих случаях у вас, во-первых, не так много выбора, а во-вторых, так как вы работаете с чем-то, что заслуживает доверия, то для вас не так важно писать скрипты, умеющие работать с файлами, названия которых содержат странные символы,
Во всех остальных случаях, увы, bash использовать не стоит.
khim
05.04.2017 20:02+1Создаётся впечатление, что это стандарт отрасли и вообще говоря у него большие шансы стать первым языком у юзера хотя бы потому что ему поневоле приходится писать 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-скприты тогда, когда вы можете быть уверены что «враг» не передаст вам плохих данных (например если вы сами и пишите и используете их), либо если у вас нет выбора. Если выбор есть, то… лучше что-нибудь другое…iig
05.04.2017 22:21прочитать список строк, сохранённых в файле filelist.zero-delim и разделённых там символом с кодом 0 и передать их в командной строке в вызове одной команды?
1. xargs?
2. Кто-то ведь зачем-то создал этот файл? Можно было бы, наверное, передавать список и без файла, через конвейер?
3. tar, например, умеет получать список файлов из файла. Может, и не нужно решать эту задачу?khim
05.04.2017 22:30+11. xargs?
xargs позволит вам решить ровно эту задачу — и ничего более. Ни фильтрации, ни обработки, ничего.
2. Кто-то ведь зачем-то создал этот файл?
Ну допустим его создали, скажем, использованиемfind
с какими-то там параметрами — вам легче стало?
Можно было бы, наверное, передавать список и без файла, через конвейер?
От этого задача стала бы только сложнее, увы.
3. tar, например, умеет получать список файлов из файла. Может, и не нужно решать эту задачу?
Во-первых он требует списка файлов разделённых'\n'
— то есть с произвольными именами файлов работать не может. А во-вторых так мы дойдём до того, что будем писать программу на bash'е, которая выглядит какperl -e '...'
.
Я весьма неплохо знаю bash, tar и прочие штуки, но давайте посмотрим правде в глаза: это корявые инструменты. Очень корявые. То, что мы научились с ними как-то жить — этой истины, увы, не отменяет…iig
05.04.2017 23:01Идеальных инструментов не бывает. Вы знаете о недостатках bash — это не делает его плохим. Просто не надо делать на нем то, к чему он не приспособлен.
Да, пример с find мне кажется натянутым. :)khim
05.04.2017 23:33Просто не надо делать на нем то, к чему он не приспособлен.
Он «не приспособлен» работать со списками файлов, среди которых может встретится «нечто странное» (пробелы, хотя бы). Что, в общем, делает его мало пригодным для чего-либо вообще: в современном мире инструмент, который может, внезапно, сломаться, если у вас в каталоге обнаружится файл с «неправильным» именем — просто слишком опасен, чтобы рекомендовать его использовать где-бы-то-ни-было… Слишком велик риск…
iig
С одной стороны, man bash я до конца так и на дочитал.
С другой стороны, даже в переводе его изучать не особо хочется.