- PVSM.RU - https://www.pvsm.ru -
Однажды вечером, перечитывая Джеффри Фридла [1], я осознал, что даже несмотря на всем доступную документацию, существует множество приемов заточенных под себя. Все люди слишком разные. И приемы, которые очевидны для одних, могут быть неочевидны для других и выглядеть какой-то магией для третьих. Кстати, несколько подобных моментов я уже описывал здесь [2].
Командная строка для администратора или пользователя — это не только инструмент, которым можно сделать все, но и инструмент, который кастомизируется под себя любимого бесконечно долго. Недавно пробегал перевод на тему удобных приемов в CLI. Но у меня сложилось впечатление, что сам переводчик мало пользовался советами, из-за чего важные нюансы могли быть упущены.
Под катом — дюжина приемов в командной строке — из личного опыта.
Маленькое отступление — в реале я использую множество приемов, в которых могут случайно встретиться имена реальных серверов или юзеров, что может попасть под NDA, поэтому я не мог копи-пастить и специально переписал и максимально упростил все примеры в статье — и если вам покажется, что какой-то прием в данном контексте совершенно бесполезен — возможно это как раз по этой причине. Но в любом случае делитесь вашими идеями в комментариях!
Часто используют 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
Без комментариев =)
Пакет bash-completion сейчас идет в поставке практически всех дистрибутивов, включить его можно или в /etc/bash.bashrc или /etc/profile.d/bash_completion.sh, но чаще всего он уже включен из коробки. В общем автокомплит по TAB — это один из тех удобных моментов, с которыми новичок знакомится в первую очередь.
А вот то, что не все активно используют, и на мой взгляд совершенно зря, так это то что автокомплит работает не только с именами файлов, а также с алиасами, именами переменных, а если копнуть в скрипты автокомплита, которые собственно являются тоже шелл скриптами, можно даже дописать автокомплит [3] для вашего скрипта или приложения. Но вернемся к алиасам.
Алиасам не нужно прописывать 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
Для более сложных случаев, часто пишутся скрипты и кладутся например в $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
Если перед командой поставить пробел, она не попадет в 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
Часто можно использовать что-то вроде
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, если вы пишете алиас/функцию/скрипт для многоразового использования.
В обычном состоянии, если приложение запущено и пишет в лог файл, его нельзя удалять. Поскольку в *nix, открытый файловый дескриптор связан уже не с именем файла, а с iNode. Следовательно о том, что файл удален в каталоге, приложение никак не узнает, и будет писать в ранее открытый дескриптор. Затем, когда приложение остановится и закроет дескриптор, данные удалятся с файловой системы.
Поэтому очистку файла следует делать либо так (очистится весь файл):
> application.log
Либо так (файл будет урезан до указанного размера):
truncate --size=1M application.log
Но команда выше именно урежет, что означает, что в файле останутся старые данные, а свежие как раз и будут урезаны.
Поэтому можно делать вот так, сохраняя последние 1000 строк:
STRING=`tail -n 1000 application.log`;echo "$STRING" > application.log
P.S. В данном примере мы не рассматриваем самый правильный способ — когда приложение само следит за своим лог-файлом пользуясь, например, log4j, или своим велосипедом или rotatelogs.
Бывает ситуация, когда ждешь какого-то события. Например, пока подключится пользователь (жмешь who несколько десятков раз), или кто-то скопирует по ftp файл (жмешь ls десятки раз).
Можно использовать
watch <команда>
По умолчанию, команда будет выполняться каждые 2 секунды с очисткой экрана, пока не нажать Ctrl+C. Частоту выполнения можно изменить опцией при запуске.
Очень полезно, когда работаешь на одном сервере
Есть удобная возможность создавать диапазоны значений, например вместо такого:
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
Ранее упоминался 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, он автоматически завершится при рестарте приложения.
Часто пользуются dd
dd if=/dev/zero of=out.txt bs=1M count=10
Но я рекомендую использовать fallocate:
fallocate -l 10M file.txt
На файловых системах, которые поддерживают аллокацию места (xfs, ext4, Btrfs...), данная команда будет выполнена мгновенно, в отличие от dd.
Всего два момента, которые полезны, если мы обрабатываем очень большой список.
Первое — список может просто не влезть в командную строку.
Но мы можем ограничить обработку аргументов через опцию -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`
Вместо 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
На этом я прощаюсь, и буду благодарен за участие в опросе.
И конечно — жду разумной критики и новых приемов от вас!
Автор: Сергей Кулик
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/bash/266327
Ссылки в тексте:
[1] Джеффри Фридла: https://scanlibs.com/regulyarnyie-vyirazheniya-3-e-izdanie/
[2] здесь: https://habrahabr.ru/post/339246/
[3] дописать автокомплит: https://habrahabr.ru/post/115886/
[4] Источник: https://habrahabr.ru/post/340544/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.