Дюжина приемов в Linux, которые действительно сэкономят уйму времени

в 9:22, , рубрики: bash, linux bash scripts, Linux для всех, shell scripting, Настройка Linux, системное администрирование
image

Однажды вечером, перечитывая Джеффри Фридла, я осознал, что даже несмотря на всем доступную документацию, существует множество приемов заточенных под себя. Все люди слишком разные. И приемы, которые очевидны для одних, могут быть неочевидны для других и выглядеть какой-то магией для третьих. Кстати, несколько подобных моментов я уже описывал здесь.

Командная строка для администратора или пользователя — это не только инструмент, которым можно сделать все, но и инструмент, который кастомизируется под себя любимого бесконечно долго. Недавно пробегал перевод на тему удобных приемов в CLI. Но у меня сложилось впечатление, что сам переводчик мало пользовался советами, из-за чего важные нюансы могли быть упущены.

Под катом — дюжина приемов в командной строке — из личного опыта.

Маленькое отступление — в реале я использую множество приемов, в которых могут случайно встретиться имена реальных серверов или юзеров, что может попасть под NDA, поэтому я не мог копи-пастить и специально переписал и максимально упростил все примеры в статье — и если вам покажется, что какой-то прием в данном контексте совершенно бесполезен — возможно это как раз по этой причине. Но в любом случае делитесь вашими идеями в комментариях!

1. Разбиение строки

Часто используют cut или даже awk, чтобы просто получить значение какого-то одного столбца.
Но в простых случаях более чем достаточно просто отрезать у переменной лишнее при помощи #, ##, % и %%:
Пример ниже показывает, как из строки «username:homedir:shell» можно получить только третий столбец (shell) при помощи cut или при помощи ##:

$ STRING="username:homedir:shell"
$ echo $STRING|cut -d ":" -f 3
shell
$ echo ${STRING##:*}
shell

Второй вариант не запускает еще один процесс cut, и вообще не использует пайпы, что должно работать гораздо быстрее. А если подобный скрипт выполнить в bash-подсистеме на windows, где пайпы еле шевелятся, разница в скорости будет огромна.
Давайте посмотрим пример на Ubuntu — крутим нашу команду в цикле 1000 раз

$ cat test.sh
#!/bin/bash

STRING="Name:Date:Shell"
echo using cut
time for A in {1..1000}
do
  echo $STRING | cut -d ":" -f 3 > /dev/null
done

echo using ##
time for A in {1..1000}
do
 echo ${STRING##*:} > /dev/null
done

Результат выполнения

$ ./test.sh
using cut
real    0m1.064s
user    0m0.012s
sys     0m0.324s
using ##
real    0m0.026s
user    0m0.016s
sys     0m0.008s

Разница — почти в 50 раз!

Конечно, пример выше — слишком искусственный. В реальной жизни мы будем обрабатывать реальный файл. А в этом случае из него нужно читать. И для cut мы просто перенаправим вывод cat через конвейер. А в случае использования ##, нам придется создать цикл с использованием read и перенаправлением в него. Итак, кто победит в этом варианте?

$ cat test.sh
#!/bin/bash
echo using cut
time for count in {1..1000}
do
  cat /etc/passwd | cut -d ":" -f 7 > /dev/null
done

echo using ##
time for count in {1..1000}
do
  while read
  do
    echo ${REPLY##*:} > /dev/null
  done < /etc/passwd
done
Результат выполнения

$ ./test.sh
using cut
real    0m1.007s
user    0m0.004s
sys     0m0.304s
using ##
real    0m0.638s
user    0m0.524s
sys     0m0.108s

Без комментариев =)

2. Автокомплит с использованием TAB

Пакет bash-completion сейчас идет в поставке практически всех дистрибутивов, включить его можно или в /etc/bash.bashrc или /etc/profile.d/bash_completion.sh, но чаще всего он уже включен из коробки. В общем автокомплит по TAB — это один из тех удобных моментов, с которыми новичок знакомится в первую очередь.
А вот то, что не все активно используют, и на мой взгляд совершенно зря, так это то что автокомплит работает не только с именами файлов, а также с алиасами, именами переменных, а если копнуть в скрипты автокомплита, которые собственно являются тоже шелл скриптами, можно даже дописать автокомплит для вашего скрипта или приложения. Но вернемся к алиасам.
Алиасам не нужно прописывать PATH, не нужно создавать отдельные исполняемый файл — они комфортно могут лежать в .profile или .bashrc.
В *nix обычно используется lowercase для файлов и каталогов, поэтому мне показалось удобным использовать алиасы с использованием uppercase — тогда автокомплит угадывает вашу мелодию с первой ноты работает буквально с первых букв:

$ alias TAsteriskLog="tail -f /var/log/asteriks.log"
$ alias TMailLog="tail -f /var/log/mail.log"
$ TA[tab]steriksLog
$ TM[tab]ailLog

3. Автозавершение с использованием TAB — 2

Для более сложных случаев, часто пишутся скрипты и кладутся например в $HOME/bin. Но есть же функции. Им опять же не нужен PATH, и для них тоже работает автокомплит.
Поместим функцию LastLogin в .profile (не забудьте перегрузить .profile):

function LastLogin {
  STRING=`last | head -n 1 | tr -s " " " "`
  USER=`echo $STRING|cut -d " " -f 1`
  IP=`echo $STRING|cut -d " " -f 3`
  echo "User: $USER, IP: $IP"
}

Затем в консоли:

$ L[tab]astLogin
User: saboteur, IP: 10.0.2.2

4. Sensitive data

Если перед командой поставить пробел, она не попадет в bash history, следовательно если в командной строке нужно ввести пароль открытым текстом, лучше такую команду исключить из истории — на примере ниже, echo «hello 2» в истории не появляется:

$ echo "hello"
hello
$ history 2
 2011  echo "hello"
 2012  history 2
$  echo "my password secretmegakey"
my password secretmegakey
$ history 2
 2011  echo "hello"
 2012  history 2

В скриптах sensitive data следует выносить в отдельный внешний файл, с правами доступа типа 600, и который добавлен в .gitignore и через source вызывать в основном скрипте:

secret.sh
PASSWORD=LOVESEXGOD

myapp.sh
. secret.sh
runapplication --user=user --password=$PASSWORD

И наконец вариант шифровать данные (пароли сертификатов, пароли для доступа к удаленным системам, пароли от SQL) используя openssl и мастер ключ.
openssl поддерживает все необходимые опции, чтобы использовать его для шифрования паролей прямо в скриптах. Пример шифрования и дешифрования:

Файл secret.key хранит только одну строку:

$ echo "secretpassword" > secret.key; chmod 600 secret.key

Шифруем нашу строку алгоритмом aes-256-cbc, c использованием случайной -salt:

$ echo "string_to_encrypt" | openssl enc -pass file:secret.key -e -aes-256-cbc -a -salt
U2FsdGVkX194R0GmFKCL/krYCugS655yLhf8aQyKNcUnBs30AE5lHN5MXPjjSFML

Такую строку можно смело вставлять в скрипт или в конфигурационный файл, который безопасно положить в .git — без secret.key его расшифровать будет сложно.
Перед использованием дешифровать в первоначальный вид можно той же командой, заменив только опцию -e на -d:

$ echo "U2FsdGVkX194R0GmFKCL/krYCugS655yLhf8aQyKNcUnBs30AE5lHN5MXPjjSFML" | openssl enc -pass file:secret.key -d -aes-256-cbc -a -salt
string_to_encrypt

5. Просмотр логов и grep

Часто можно использовать что-то вроде

tail -f application.log | grep -i error

Или даже так

tail -f application.log | grep -i -P "(error|warning|failure)"

Но не забывайте про опцию -v, которая выводит наоборот — строки, которые НЕ соответствуют шаблону — это позволяет вывести не только строку с проблемой, но и в случае exception все остальные строки, которые к нему относятся вот таким образом (исключаем все строки, в которых есть info, выводим все остальные):

tail -f application.log | grep -v -i "info"

Дополнительные нюансы:
Не забывайте использовать -P, так как по умолчанию grep использует basic regular expression, а не PCRE, и вы можете столкнуться с тем, что просто так группы не работают.

Не забывайте и про другие популярные опции "--line-buffered", "-i".

Если вы знаете регулярные выражения, то при помощи --only-matching, можно значительно улучшить вывод. Но в принципе это редко используется в случае разового использования. Зато очень рекомендую почитать мануал по grep, если вы пишете алиас/функцию/скрипт для многоразового использования.

6. Уменьшение размера лог файлов

В обычном состоянии, если приложение запущено и пишет в лог файл, его нельзя удалять. Поскольку в *nix, открытый файловый дескриптор связан уже не с именем файла, а с iNode. Следовательно о том, что файл удален в каталоге, приложение никак не узнает, и будет писать в ранее открытый дескриптор. Затем, когда приложение остановится и закроет дескриптор, данные удалятся с файловой системы.
Поэтому очистку файла следует делать либо так (очистится весь файл):

> application.log

Либо так (файл будет урезан до указанного размера):

truncate --size=1M application.log

Но команда выше именно урежет, что означает, что в файле останутся старые данные, а свежие как раз и будут урезаны.
Поэтому можно делать вот так, сохраняя последние 1000 строк:

STRING=`tail -n 1000 application.log`;echo "$STRING" > application.log

P.S. В данном примере мы не рассматриваем самый правильный способ — когда приложение само следит за своим лог-файлом пользуясь, например, log4j, или своим велосипедом или rotatelogs.

7. watch следит за тобой!

Бывает ситуация, когда ждешь какого-то события. Например, пока подключится пользователь (жмешь who несколько десятков раз), или кто-то скопирует по ftp файл (жмешь ls десятки раз).
Можно использовать

watch <команда>

По умолчанию, команда будет выполняться каждые 2 секунды с очисткой экрана, пока не нажать Ctrl+C. Частоту выполнения можно изменить опцией при запуске.
Очень полезно, когда работаешь на одном сервере

8. Последовательности

Есть удобная возможность создавать диапазоны значений, например вместо такого:

for srv in 1 2 3; do echo server${srv};done
1
2
3

использовать вот такое:

for srv in server{1..3}; do echo $srv;done
server1
server2
server3

А еще можно использовать команду seq, чтобы форматировать вывод. Например выравниваем ширину автоматически:

for srv in `seq -w 1 10`; do echo server${srv};done
server01
server02
server03
server04
server05
server06
server07
server08
server09
server10

А вот еще один пример конструкции с фигурными скобками, которая позволит массово переименовать файлы. Для получения имени файла без расширения используем basename:

for file in *.txt; do name=`basename $file .txt`;mv $name{.txt,.lst}; done

А можно еще короче, с использованием %:

for file in *.txt; do mv ${file%.txt}{.txt,.lst}; done

9. tail, несколько файлов и несколько юзеров

Ранее упоминался multitail, который может следить за несколькими файлами сразу. Но он не поставляется из-под коробки, а права для установки есть не всегда.
Но с этим вполне может справиться и обычный tail:

tail -f /var/logs/*.log

Кстати, вернемся на минуту к алиасам с «tail -f».
Бывает, что на сервере, где крутится некое приложение, лазят разные тестировщики, разработчики и все смотрят лог приложения через tail -f. Даже на продакшене несколько саппортеров-индусов могут смотреть один и тот же лог каждый из своей сессии.
При перезапуске приложения, остаются висящие «tail -f», которые могут висеть несколько дней. Это не проблема, но не аккуратненько.
Полезно будет сделать алиас, который получает PID вашего приложения из PID файла, и автоматически завершит tail при завершении процесса:

alias TFapplicationLog='tail -f --pid=`cat /opt/myapp/tmp/app.pid` /opt/app/logs/application.log'

И добавить этот алиас во все профайлы. Даже если разработчики ушли домой, забыв остановить свой tail, он автоматически завершится при рестарте приложения.

10. создание файла нужного размера

Часто пользуются dd

dd if=/dev/zero of=out.txt bs=1M count=10

Но я рекомендую использовать fallocate:

fallocate -l 10M file.txt

На файловых системах, которые поддерживают аллокацию места (xfs, ext4, Btrfs...), данная команда будет выполнена мгновенно, в отличие от dd.

11. xargs

Всего два момента, которые полезны, если мы обрабатываем очень большой список.
Первое — список может просто не влезть в командную строку.
Но мы можем ограничить обработку аргументов через опцию -n:

$ # создаем файл из 5 строк
for string in string{1..5}; do echo $string >> file.lst; done
$ cat file.lst
string1
string2
string3
string4
string5
saboteur@ubuntu:~$ cat file.lst | xargs -n 2
string1 string2
string3 string4
string5

Второе — команда может выполняться слишком долго, ибо мы запустили ее выполняться в один поток. А если у нас несколько ядер, то полезно запускать xargs в три потока, каждый будет обрабатывать по 2 аргумента:

cat file | xargs -n 2 -P 3

Если мы хотим запустить на все доступные ядра, то можно даже использовать nproc, скрипт автоматически определит количество доступных ядер на текущей машине:

cat file | xargs -n 2 -P `nproc`

12. sleep? while? read!

Вместо sleep или бесконечного цикла while, я пишу read, что позволяет одной командой сделать паузу, которую можно в любой момент прервать:

read -p "Press any key to continue " -n 1

Или добавить таймаут, который также можно в любой момент прервать и продолжить выполнение:

read -p "Press any key to continue (autocontinue in 30 seconds) " -t 30 -n 1

Можно усложнить конструкцию до полноценной обработки:

REPLY=""
until [ "$REPLY" = "y" ]; do
  # executing some command
  read "Press 'y' to continue or 'n' to break, any other step to repeat this step" -n 1
  if [ "$REPLY" = 'n' ]; then exit 1; fi
done

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

Автор: Сергей Кулик

Источник

Поделиться

* - обязательные к заполнению поля