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.
fi
switch-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
done
FLAGS=(foo bar baz)
for f in $FLAGS; do
echo $f
done
Можно фориться по списку файлов:
for file in *.gif; do
echo $file
done
while
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 $FILE
IFS
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
done
srd: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 # readme
awk
Вообще-то это целый язык программирования, но в большинстве случаев он используется просто чтобы распарсить колоночный вывод от других программ:
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 three
grep
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.0
pkgconfig
знает только про те либы, для которых есть 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 # перезапуск systemd
journalctl -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
в удобочитаемом формате.