sh
- Parameter expansion
 - Чтение переменных
 - Специальные переменные
 - Условия
 - switch-case
 - 
- System V
 - systemd
 - Выход из скрипта после первой провалившейся команды
 - Перемещение в директорию, в которой лежит скрипт
 - here document
 - Сравнение бинарных файлов
 - Вывод кода нажатой клавиши
 - Вывод процессов, отжирающих больше всего памяти
 - Показать, сколько осталось свободной памяти
 - Отправка почты
 - Вывод процесса, занимающего порт:
 - Вывод файлов, занятых процессом
 - Вывод процесса, занимающего файл
 - Вывод размера директории:
 - Логирование вывода в консоль и в файл
 - Вывод информации о дистрибутиве
 - Использование результатов команд в качестве файлов
 - Редактирование предыдущей команды
 - tr
 
 
Parameter expansion
for i in 1 2 
do
	my_secret_process ${i}_tmp
doneТакая форма записи позволит подставить 1tmp и 2tmp. Если делать без скобок, то получится пустота, так как у нас нет переменной i_tmp
Чтение переменных
FOO=bar
echo "Hello, $FOO!"   # Hello, bar! - сработает только с двойными кавычками, так как они слабые
echo 'Hello, $FOO!'   # Hello, $FOO! - не сработает, так как одинарные кавычки - строгиеСпециальные переменные
$0- название скрипта$1, $2, $3...- первый, второй, третий аргумент$#- количество аргументов$@- все аргументы$$- текущий PID$?- exit code последней выполненной команды
Условия
if [ "$1" = "hi" ]; then
	echo "hello"
else
	echo "bye"
fiКавычки вокруг $1 и hi не обязательнц, но нужны на случай, если в $1 будет пустота, либо слова со спец. символами.
Причем [ здесь - это симлинк к команде test:
$ type -a [
[ is a shell builtin
[ is /bin/[Таким образом, if просто проверяет, равен ли 0 результат следующего за ним test.
Проверки для файлов:
-f: есть ли такой обычный файл на диске (if [ -f 'filename.txt' ])-d: есть ли такая директория-e: есть ли такой файловый дескриптор-s: true, если файл есть и не пустой-h: есть ли такой симлинк-a: логическое "и" (if [ -f file1 -a file2 ])-o: логическое "или"-r: читаемый-w: редактируемый-x: выполняемый-nt: newer than (if [ file1 -nt file2 ])-ot: older than
Все эти команды (кроме -h) поддерживают симлинки. То есть если link это симлинк на файл, то [ -f link ] вернет 0.
Проверки для строк:
-z: true, если строка пуста-n: true, если строка не пуста
Арифметические проверки:
ВАЖНО: = проверяет на равенство только строки, но не числа.
Обратить условие можно, добавив ! перед ним: if [ ! -f 'filename.txt' ]. Для проверки чисел на равенство, нужно использовать -eq.
-eq: equal-ne: not equal-lt: less than-gt: greater than-le: less than or equal-ge: greater than or equal
Вслед за if может идти любая команда, поэтому и нужна ";" перед then. Если не указать точку с запятой, то then будет распознана как аргумент команды. Можно не писать точку с запятой, но тогда then должен быть на следующей строчке.
Пример другой команды:
if grep -q daemon /etc/passwd; then
   echo The daemon user is in the passwd file.
else
   echo There is a big problem. daemon is not in the passwd file.
fiswitch-case
case $1 in
	bye)
		echo Fine, bye.
		;; 
	hi|hello)
		echo Nice to see you.
		;;
	what*)
		echo Whatever.
		;; 
	*)
		echo 'Huh?'
		;; 
esacЦиклы
for
for str in one two three four; do
   echo $str
doneFLAGS=(foo bar baz)
for f in $FLAGS; do
	echo $f
doneМожно фориться по списку файлов:
for file in *.gif; do
	echo $file
donewhile
FILE=/tmp/whiletest.$$;
echo firstline > $FILE
while tail -10 $FILE | grep -q firstline; do
	# add lines to $FILE until tail -10 $FILE no longer prints "firstline" 
	echo -n Number of lines in $FILE:' '
	wc -l $FILE | awk '{print $1}'
	echo newline >> $FILE
done
rm -f $FILEIFS
IFS = internal field separator. Значение этой переменной используется для представления строки в виде массива.
Пример (не сработает в zsh, сработает в bash):
words=foo,bar,baz
for word in $words
do
	echo $word
doneВ результате будет выведено:
foo
bar
bazПодстроки
FOO=Hello!
BAR={FOO:1:3}	# взять 3 символа, начиная с 1-го. то есть получится "ell"Если второе число не указано, то возьмется до конца строки:
FOO=Hello!
BAR={FOO:3}	# lo!Парсинг опций
# command.sh
while getopts 'srd:f:' c
do
  case $c in
    s) ACTION=SAVE ;;
    r) ACTION=RESTORE ;;
    d) DB_DUMP=$OPTARG ;;
    f) TARBALL=$OPTARG ;;
  esac
donesrd:f: - это список флагов, которые могут быть использованы. Причем если после флага стоит двоеточие, то для этого флага можно указать значение.
Скрипт из примера можно запускать с такими параметрами:
./command.sh -sr -d dump.db -f ball.TARBALLСтили и цвета
Для использования стилей текста сильно поможет такая функция:
#!/bin/bash
# ANSI color--Use these variables to make output in different colors
# and formats. Color names that end with 'f' are foreground colors,
# and those ending with 'b' are background colors.
initializeANSI()
{
 esc="\033" # If this doesn't work, enter an ESC directly.
 # Foreground colors
 blackf="${esc}[30m"; redf="${esc}[31m"; greenf="${esc}[32m"
 yellowf="${esc}[33m" bluef="${esc}[34m"; purplef="${esc}[35m"
 cyanf="${esc}[36m"; whitef="${esc}[37m"
 # Background colors
 blackb="${esc}[40m"; redb="${esc}[41m"; greenb="${esc}[42m"
 yellowb="${esc}[43m" blueb="${esc}[44m"; purpleb="${esc}[45m"
 cyanb="${esc}[46m"; whiteb="${esc}[47m"
 # Bold, italic, underline, and inverse style toggles
 boldon="${esc}[1m"; boldoff="${esc}[22m"
 italicson="${esc}[3m"; italicsoff="${esc}[23m"
 ulon="${esc}[4m"; uloff="${esc}[24m"
 invon="${esc}[7m"; invoff="${esc}[27m"
 reset="${esc}[0m"
}Использовать ее можно вот так:
initializeANSI
echo ${boldon}this is in bold and ${italicson}this is \
italics${italicsoff}within the bold${reset}Утилиты
basename
Позволяет получить имя файла без пути, или имя файла без расширения, или и то и то.
basename ~/temp/readme.md # readme.md
basename readme.md .md # readme
basename ~/temp/readme.md .md # readmeawk
Вообще-то это целый язык программирования, но в большинстве случаев он используется просто чтобы распарсить колоночный вывод от других программ:
ls -l | awk '{print $5}'   # выведет 5-ю колонку, то етсь размерЕсли нужно вывести несколько колонок:
ls -l | awk '{print $6 " " $7 " " $8}'   # выведет 5,6,7 колонку через пробелsed
sed принимает первым аргументом адрес и операцию, а вторым - путь к файлу. Либо можно пайплайнить в него вывод другой программы, тогда ворой аргумент не нужен:
ls -l | sed 's/июл/авг/'  # заменяет месяц в выводе с июля на августsed работает построчно, и по умолчанию он будет заменять первое встретившееся значение в каждой строчке. Если нужно чтобы точно заменил все значения, то в конце надо приписывать модификатор g:
sed 's/foo/bar/g' tmp.txtВ предыдущих примерах адрес был опущен. В качестве адреса могут быть указаны, например, первая и последняя строчка, над которыми проводить операцию:
sed '1,3s/foo/bar/g' tmp.txt 	# замена произойдет в строчках 1-3
sed '2,4d' tmp.txt 				# будут удалены строчки 2-4Вместо адреса может быть использовано даже регулярное выражение:
sed '/exp/d' tmp.txt	# удалит все строки, подходящие под указанный регэкспxargs
Принимает на вход аргументы, разделяет по пробелам, табам, переводам строки и подставляет их в команду последним аргументом
ls | xargs file 	# выведет тип для каждого файла в текущей директорииЧасто бывает, что xargs не хочет разделять аргументы, руководствуясь какой-то своей внутренней логикой:
$ echo 'one two three' | xargs echo mkdir 
mkdir one two threeВ этих случаях надо явно указать количество элементов в группе опцией -n:
$ echo 'one two three' | xargs -n1 echo mkdir      
mkdir one
mkdir two
mkdir threeАргументы, поступающие в xargs можно подставлять не только последним, но и любым аргументом, но тогда нужно использовать такую конструкцию:
ls | xargs -I % file %Здесь -I % означает, что плейсхолдер будет обозначаться знаком процента, а % в конце - это как раз плейсхолдер.
Но тут есть подвох. man xargs говорит нам, что при использовании опции -I:
unquoted blanks do not terminate input items; instead the separator is the newline characterТо есть в этом случае символом-разделителем считается только лишь символ перевода строки. Чтобы побороть это поведение, нужно явно указать символ-разделитель опцией -d. Обратите внимание, что мы тут указываем знк пробела в качестве разделителя, но его еще нужно экранировать, поэтому после -d стоит бэкслэш и ДВА пробела: 
$ echo 'one two three' | xargs -n1 -I{} -d\  echo mkdir {}
mkdir one
mkdir two
mkdir threegrep
grep -R def . - рекурсивно ищет "def" во всех файлах текущей директории
grep -E [a-z]{10} - регулярное выражение
grep a[[:blank:]] - ищем все слова, кончающиеся на а
grep -c PATTERN - выводит не строчки, а количество вхождений паттерна в файле
find
find . -name "*setup*" - рекурсивно искать все файлы с именем, содержащим setup, начиная с текущей директории
find -type f -exec grep PATTERN {} \; : -exec позволяет выполнить указанную после него команду для каждого найденного файла. {} - плейсхолдер для полного пути файла. ; - конец команды. 
find / -name FILENAME -print 2>/dev/null : поиск файла FILENAME по всей файловой системе
tar
Распаковка архива
tar -xvf file.tar
Если имеет расширение .tar.gz , то:
tar -xzvf file.tar.gz
Если .tar.bz2 :
tar -xjvf file.tar.bz2
-x : распаковка архива
-v : verbose-вывод и показ прогресса
-f : из файла
-j : архив bzip2
-z : архив gzip
Если нужно экстрактнуть 1 файл, то:
tar -xvf file.tar foo.txt
Работа с пакетами
Поиск файлов, установленных пакетом
dpkg -L <имя пакета>
Пример:
apt-get install yasm 
dpkg -L yasmПоиск пакета, установившего файл
dpkg -S /usr/lib/python3.4/bz2.py
Поиск пакета, содержащего файл
Ищет даже по не установленным пакетам, то есть можно использовать, когда нужно узнать, какой пакет нужно установить для отсутствующего заголовка или утилиты.
apt-file search pkg.h
Установка deb-пакетов
dpkg -i NAME.deb
sudo apt-get -f installПосле первой строчки может выдать ошибки - не волноваться, вторая все исправит.
apt-get
Запустить симуляцию установки:
apt-get install -s PACKAGEВывод установленной версии пакета:
apt-cache policy PACKAGEРабота с либами
Вывод .so, которые нужны библиотеке и путей, по которым она их ищет:
ldd file.soВывод всех папок, в которых gcc ищет хидеры:
LC_ALL=C gcc -v -E -xc - > /dev/null 2>&1 |
LC_ALL=C sed -ne '/starts here/,/End of/p'Вывод флагов компиляции для установленной библиотеки
pkg-config --cflags --libs LIBRARY_NAMEПример использования:
gcc -Wall -o main main.cpp $(pkg-config --cflags --libs gstreamer-1.0)После выполнения выражения, получим:
gcc -Wall -o main main.cpp -pthread -I/usr/include/gstreamer-1.0 -I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -lgstreamer-1.0 -lgobject-2.0 -lglib-2.0pkgconfig знает только про те либы, для которых есть pc файл в одной из его поисковых директорий. Список таких директорий можно узнать командой:
pkg-config --variable pc_path pkg-configПоиск библиотеки на диске
ldconfig -p | grep gstreamerВыведет путь к либе, если найдет такую.
Службы
System V
service --status-all # вывод всех служб
service <service> start/stop # старт/остановка службы
service <service> status # вывод статуса службыsystemd
systemctl # вывод всех служб
systemctl --failed # вывод упавших служб
systemctl start/stop/reload/restart/status [service-name] # запуск/остановка/чтение конфига/перезапуск/вывод статуса службы
systemctl enable/disable [service-name] # включение/выключение службы
systemctl is-enabled/is-active [service-name] # проверка, запускается ли на старте системы/запущена ли сейчас служба
systemctl daemon-reload # перезапуск systemdjournalctl -t [service-name] # вывод логов службы
tail -f /var/log/messages # вывод логов для служб, которые не пишут в journalctl
tail -f /var/log/secure # вывод логов для привилегированных служб
tail -f /var/log/[service-name] # если служба не пишет в /var/log/messages, возможно у нее свой личный лог
## Tips & tricks
### Отладка
`sh -n <script>` -проверка на синтаксические ошибки
`sh -v <script>` - выводит все команды на экран перед выполнением
`sh -x <script>` - тоже выводит, но после подстановки всех переменных и прочей обработки
`sh -u <script>` - выводит ошибку, если используется неопределенная переменная
```sh
set -o xtrace
...
set +o xtraceТакой код можно написать вокруг проблемного участка кода, чтобы только в этом коде выводить на экран команды с подстановкой переменных.
trap ‘echo Exiting: critical variable = $critical_variable’ EXIT - а так мы можем выводить значение интересующей нас переменной перед выходом из скрипта
Выход из скрипта после первой провалившейся команды
Нужно вставить в начало скрипта:
set -eПеремещение в директорию, в которой лежит скрипт
cd "${0%/*}"Если нужно в другую папку, относительно скрипта (например, в корень репозитория), то можно в конце добавлять относительный путь, например:
cd "${0%/*}/.."https://stackoverflow.com/questions/28894290/what-does-cd-0-mean-in-bash
here document
Используя специальный синтаксис, можно прямо в команду вставить текст, который будет сохранен в файл:
$ cat > test.txt << EOF
> hello
> this
> is
> test
> file
> EOFЗдесь EOF - это плейсхолдер, обозначающий последнюю строчку файла. Он необязательно должен быть EOF, может быть любой.
Сравнение бинарных файлов
cmp -l file1.bin file2.bin | gawk '{printf "%08X %02X %02X\n", $1, strtonum(0$2), strtonum(0$3)}'Вывод кода нажатой клавиши
showkey -aВыводит в том числе и коды сочетаний клавиш, например, Ctrl+Shift+F8
Вывод процессов, отжирающих больше всего памяти
ps aux --sort=-%mem | head
Показать, сколько осталось свободной памяти
free -m
В колонке available показано, сколько памяти доступно процессам
Отправка почты
apt-get install mailutils ssmtp
nano /etc/ssmtp/smtp.confТам вписать строчки:
root=yuriy.timofeev@vocord
mailhub=smtp.gmail.com:465
FromLineOverride=YES
AuthUser=yuriy.timofeev@vocord.ru
AuthPass=testing123
UseTLS=YESОтправка письма:
echo "Тело письма" | mail -s "Пришел" -a "From: Юрий Тимофеев<yuriy.timofeev@vocord.ru>" yuriy.timofeev@vocord.ruВывод процесса, занимающего порт:
lsof -i -P -n | grep :<port>;Вывод файлов, занятых процессом
lsof -p <pid>Вывод процесса, занимающего файл
lsof <path>Вывод размера директории:
du -k DIRECTORY-kвыводит размер в килобайтах-b,-m- в байтах и мегабайтах, соответственно
Логирование вывода в консоль и в файл
Нужно вставить в начало скрипта:
exec > >(tee script.log)
exec 2>&1Вывод информации о дистрибутиве
$ uname –srmpio
Linux 3.13.0-36-generic x86_64 x86_64 x86_64 GNU/LinuxЛибо
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.1 LTS"Использование результатов команд в качестве файлов
Конструкция b <(a) запустит команду a, сгенерит временный файл для ее результата и передаст имя файла в b.
Пример:
diff <(journalctl -b -1 | head -n20) <(journalctl -b -2 | head -n20)Редактирование предыдущей команды
$ touch a.txt
$ ^txt^log
$ touch a.logПоследняя команда будет не выполнена, но автоматически подставлена для ввода после выполнения второй команды.
Так же есть команда fc. Она открывает в текстовом редакторе последнюю выполненную команду, где ее можно отредактировать и после выхода будет выполнен отредактированный вариант.
tr
Заменяет или удаляет символы из входного потока.
Пример:
echo $PATH | tr -s ':' '\n'Команда заменила все : на символы перевода строки и вывела PATH в удобочитаемом формате.