
Пишем функцию быстрого доступа к сложным командам для Zsh и Bash
Приветствую!
В этой статье мы создадим функцию для оболочек Zsh и Bash, которая позволит быстро и удобно получить доступ к подготовленному списку сложных, длинных и трудно запоминаемых команд🤯. Ну и добавим немного интерактивности с помощью клавиши Tab
😉.
Подписывайтесь на наш телеграм @r4ven_me📱, чтобы не пропустить новые публикации на сайте😉. А если есть вопросы или желание пообщаться по тематике – заглядывайте в Вороний чат @r4ven_me_chat🧐.
В чем суть?
⚠️Рассматриваемая ниже функция для Zsh является частью моего файла .zshrc, про тонкую настройку которого я рассказывал в предыдущей статье🔗. В своей конфигурации я использую Zsh совместно с фреймворком Oh-My-Zsh.
Думаю многие пользователя Linux🐧 в своей ежедневной рутине пользуются alias
‘ми (псевдонимами) оболочки для быстрого доступа к сложным командам🤯. Я из их числа😌
Но в какой-то момент список таких алиасов становится слишком большим и запоминать их становится все труднее. Да, их можно выводить с помощью одноименной команды alias
с последующей фильтрацией, но лично мне это не удобно🤷♂️.
Немного заскучав подумав, как можно упростить себе жизнь, решил создать простую, но удобную функцию оболочки, которую достаточно поместить в условный .zshrc
или .bashrc
.
Что-то типа быстрых команд/кнопок в графических SSH клиентах, наподобие Xshell или Asbru только в консоли🖥️.
Вот что делает эта функция:
- содержит массив из коротких и понятных ключей-имен, в значении которых расположены, не редко зубодробительные, команды;
- выводит весь список доступных команд с помощью ключа
-h
; - умеет в интерактивность с помощью “пролистывания” вариантов клавишей Tab (только Zsh);
- после нажатия
Enter
команда не выполняется, а вводится в качестве следующей команды, но без выполнения, чтобы ее можно было отредактировать при необходимости.
Ниже небольшая демонстрация работы функции, созданная с помощью asciinema плеера:
⚠️Для корректного отображения цветов отключите темный режим на сайте.
Имя команды-функции —
cmd
.
Ниже приведу примеры кода для оболочек Zsh и Bash, который нужно добавить в ваш .*rc
файл и наполнить массив cmd_list
своими командами в формате Zsh:
short_name "long_command"
или в формате Bash:
[short_name]="long_command"
Сразу предупреждаю, что чувствительные для оболочки спец. символы, используемые в мудреных командах — нужно экранировать. В каких-то случаях с помощью кавычек, в иных обратной косой чертой. Пример: \$
А теперь переходим к коду🧑💻
Добавление функции в файл .zshrc
/.bashrc
В зависимости от используемого вами командного интерпретатора, добавьте код из соответствующего блока ниже👇 в ваш конфигурационный файл .*rc
🗒️.
Код для файла .zshrc
Открываем на редактирование конфигурационный файл Zsh:
nvim ~/.zshrc
И добавляем в него следующий код:
# Функция cmd для запуска кастомных команд
# Принимает один аргумент - имя команды
cmd() {
# Локальная переменная для хранения имени команды
local cmd_name="${1}"
# Ассоциативный массив для хранения списка команд
typeset -A cmd_list
# Ключи - это имена команд, значения - сами команды
cmd_list=(
ps_top5_cpu "ps --sort=-%cpu -eo user,pid,ppid,state,comm | head -n6"
ps_top5_mem "ps --sort=-%mem -eo user,pid,ppid,state,comm | head -n6"
cron_add '{ crontab -l; echo "0 3 * * 0 ls -l &> dirs.txt"; } | crontab -'
du_top20 'du -h / 2> /dev/null | sort -rh | head -n 20'
journal_vacuum 'journalctl --vacuum-size=800M'
lsof_opened 'lsof +D /opt'
ss_listen "ss -tuln | awk '{print \$5}' | grep -Eo ':[0-9]+$' | sort -t: -k2 -n -u"
sed_replace "sed -i 's/old_text/new_text/g' file.txt"
find_chmod_f "find /path -type f -exec chmod 644 {} \\;"
find_chmod_d "find /path -type d -exec chmod 755 {} \\;"
ossl_encrypt_tar 'tar -czf - /var/log/apt | openssl enc -aes-256-cbc -pbkdf2 -e -out ./logs.tar.gz.enc'
ossl_decrypt_file 'openssl enc -aes-256-cbc -pbkdf2 -d -in ./logs.tar.gz.enc -out ./logs.tar.gz'
urandom_str "cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1"
dig "dig r4ven.me +short +answer +identify"
icmp_ignore "echo 1 | sudo tee /proc/sys/net/ipv4/icmp_echo_ignore_all"
docker_network "docker network create --opt com.docker.network.bridge.name=br-mynetwork --opt com.docker.network.enable_ipv6=false --driver bridge --subnet 172.22.23.0/24 --gateway 172.22.23.1 my_network"
tcpdump_dhost_dport "sudo tcpdump -i any -nn -q dst host 10.11.12.13 and dst port 443"
tcpdump_wrtie_pacp "sudo tcpdump -nn -i any host 10.11.12.13 -w ./tcpdump.pcap"
tcpdump_read_pcap "sudo -u tcpdump tcpdump -qns 0 -X -r ./tcpdump.pcap | less"
)
# Проверка наличия команды в массиве, или если запрошена справка
if [[ -z ${cmd_list[$cmd_name]} || -z "$cmd_name" || "$cmd_name" == "-h" ]]; then
# Вывод списока доступных команд
echo "AVAILABLE COMMANDS:\n"
printf "%-20s %s\n" "Key" "Command"
echo "----------------------------"
# Перебор всех ключей в массиве с их выводом
for key in "${(@k)cmd_list}"; do
printf "%-20s %s\n" "$key" "${cmd_list[$key]}"
# echo "------------------"
done | sort
return 0
else
# Если команда найдена, происходит ее вставка в командную строку (без выполнения)
print -zr "${cmd_list[$cmd_name]}"
return 0
fi
}
# Функция для автодополнения команд
_cmd_completion() {
# Выполнение функции cmd, но только для получения ключей
local -a keys
keys=($(cmd -h | awk 'NR>4 {print $1}')) # Извлечение ключей из вывода справки
compadd "$@" -- "${keys[@]}"
}
# Регистрация функции автодополнения для команды/функции cmd
compdef _cmd_completion cmd
Сохраняем и зарываем файл. Теперь при каждом новом сеансе будет доступна новая команда — cmd
. Чтобы изменения оболочки стали доступны в текущем сеансе выполните:
exec zsh
Теперь попробуйте ввести команду:
cmd -h
Должен выводиться список доступных команд:
Теперь попробуйте “протабать” cmd
, чтобы вывести список доступных коротких ключей-имен команд и выбрать нужную:
Как по мне, очень удобно👍.
Код для файла .bashrc
Для Bash код немного другой, но принцип тот же. Аналогично открываем конфиг оболочки:
nvim ~/.bashrc
И добавляем код:
# Функция cmd для запуска кастомных команд
# Принимает один аргумент - имя команды
cmd() {
# Локальная переменная для хранения имени команды
local cmd_name="${1}"
# Ассоциативный массив для хранения списка команд
declare -A cmd_list
# Ключи - это имена команд, значения - сами команды
cmd_list=(
[ps_top5_cpu]="ps --sort=-%cpu -eo user,pid,ppid,state,comm | head -n6"
[ps_top5_mem]="ps --sort=-%mem -eo user,pid,ppid,state,comm | head -n6"
[cron_add]='{ crontab -l; echo "0 3 * * 0 ls -l"; } | crontab -'
[du_top20]='du -h / 2> /dev/null | sort -rh | head -n 20'
[journal_vacuum]='journalctl --vacuum-size=800M'
[lsof_opened]='lsof +D /opt'
[ss_listen]="ss -tuln | awk '{print \$5}' | grep -Eo ':[0-9]+$' | sort -t: -k2 -n -u"
[sed_replace]="sed -i 's/old_text/new_text/g' file.txt"
[find_chmod_d]="find /path -type f -exec chmod 644 {} \\;"
[find_chmod_f]="find /path -type f -exec chmod 755 {} \\;"
[ossl_encrypt_tar]='tar -czf - /var/log/apt | openssl enc -aes-256-cbc -pbkdf2 -e -out ./logs.tar.gz.enc'
[ossl_decrypt_file]='openssl enc -aes-256-cbc -pbkdf2 -d -in ./logs.tar.gz.enc -out ./logs.tar.gz'
[urandom_str]="cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1"
[dig]="dig r4ven.me +short +answer +identify"
[icmp_ignore]="echo 1 | sudo tee /proc/sys/net/ipv4/icmp_echo_ignore_all"
[docker_network]="docker network create --opt com.docker.network.bridge.name=br-mynetwork --opt com.docker.network.enable_ipv6=false --driver bridge --subnet 172.22.23.0/24 --gateway 172.22.23.1 my_network"
[tcpdump_dhost_dport]="sudo tcpdump -i any -nn -q dst host 10.11.12.13 and dst port 443"
[tcpdump_wrtie_pacp]="sudo tcpdump -nn -i any host 10.11.12.13 -w ./tcpdump.pcap"
[tcpdump_read_pcap]="sudo -u tcpdump tcpdump -qns 0 -X -r ./tcpdump.pcap | less"
)
# Проверка наличия команды в массиве, или если запрошена справка
if [[ -z ${cmd_list[$cmd_name]} || -z "$cmd_name" || "$cmd_name" == "-h" ]]; then
# Вывод списока доступных команд
echo "AVAILABLE COMMANDS:"
printf "%-20s %s\n" "Key" "Command"
echo "----------------------------"
# Перебор всех ключей в массиве с их выводом
for key in "${!cmd_list[@]}"; do
printf "%-20s %s\n" "$key" "${cmd_list[$key]}"
done | sort
return 0
else
# Проверяем, запущен ли скрипт в интерактивной оболочке Bash и есть ли подключенный терминал
if [[ -n "$BASH" && -t 0 ]]; then
# Получаем команду из списка команд по заданному имени и выводим ее
local command_to_insert="${cmd_list[$cmd_name]}"
bind '"\e[0n": "'"${command_to_insert}"'"'
printf '\e[5n'
else
echo "${cmd_list[$cmd_name]}"
fi
return 0
fi
}
# Функция для автодополнения команд
_cmd_completion() {
# Выполнение функции cmd, но только для получения ключей
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts=$(cmd -h | awk 'NR>4 {print $1}')
# Если текущее слово (cur) не пустое, ищем соответствующие варианты автодополнения
if [[ ${cur} == * ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
}
# Регистрация функции автодополнения для команды/функции cmd
complete -F _cmd_completion cmd
Применяем изменения для текущей сессии оболочки:
exec bash
Выводем справку:
cmd -h
И попробуйте “протабать” команду cmd
. К сожалению Bash сможет только вывести список доступных команд, но не переключать их интерактивно, как это умеет Zsh🤷♂️.
Послесловие
Сегодня наш арсенал of Linux power user получил очередной полезный инструмент в виде shell-функции, которая по сути является маленькой программой☝️.
Мне кажется получился наглядный пример программирования оболочки, в том смысле: как научить ее выполнять нужное действие еще с привязкой горячей клавиши Tab
🎹. Ну и тут хорошо заметна разница в синтаксисе оболочек Zsh и Bash при выполнении одинаковой задачи.
Не забудьте подписаться на наш телеграм канал @r4ven_me, чтобы не пропустить новых заметок на сайте😇. И конечно добавляйтесь в чат @r4ven_me_chat, где мы в своем микросообществе обсуждаем всякое из АйТи💬.
Спасибо, что читаете Вороний блог🐦⬛. Всех благ!