build / build (push) Successful in 23m2s
- das-shell 0.3.0: aliases and functions for every interactive bash (/usr/share/das/shell, loaded by /etc/bash/bashrc.d/das-aliases.sh, then the user's ~/.bash_aliases and ~/.bash_functions; off with ~/.config/das/no-aliases). Based on the author's own set, adapted to Void (xbps, nftables, das-update), with fixes (rmhost, netinfo, todo -r, dirsize, extract) and new ones (up, bigfiles, pkgowner, please, serve, weather, cheat, genpass, Docker shortcuts). lsfunc shows bilingual descriptions. Shell tests in tests/test_shell.py. - Names that shadowed commands are gone: gh -> hgrep, mount -> mounts, no install or ed. - README: "Aliases and functions" section; TODO: banner, dashboard service and prompt verified by the author on real hardware. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
375 lines
13 KiB
Bash
375 lines
13 KiB
Bash
# Das OS shell functions, loaded by /etc/bash/bashrc.d/das-aliases.sh in
|
||
# interactive shells. Your own ~/.bash_functions is read after this file.
|
||
# The first line of each function is its description for lsfunc:
|
||
# "# Українською | In English".
|
||
# shellcheck shell=bash
|
||
|
||
__das_uk() {
|
||
[[ ${LC_ALL:-${LC_MESSAGES:-${LANG:-}}} == uk* ]]
|
||
}
|
||
|
||
# __das_say UKRAINIAN ENGLISH
|
||
__das_say() {
|
||
if __das_uk; then echo "$1"; else echo "$2"; fi
|
||
}
|
||
|
||
# __das_usage UKRAINIAN ENGLISH: a usage line on stderr, returns 2
|
||
__das_usage() {
|
||
__das_say "використання: $1" "usage: $2" >&2
|
||
return 2
|
||
}
|
||
|
||
# __das_need COMMAND PACKAGE: is COMMAND there? If not, says which package has it
|
||
__das_need() {
|
||
command -v "$1" >/dev/null && return 0
|
||
__das_say "немає команди $1, встановіть: xi $2" "$1 is missing, install it: xi $2" >&2
|
||
return 127
|
||
}
|
||
|
||
lsfunc() {
|
||
# Список функцій з описами | List the functions with their descriptions
|
||
local name file line descr restore
|
||
restore=$(shopt -p extdebug)
|
||
shopt -s extdebug
|
||
while read -r name; do
|
||
[[ $name == _* ]] && continue
|
||
read -r _ line file < <(declare -F "$name")
|
||
descr=""
|
||
if [[ -r $file ]]; then
|
||
descr=$(sed -n "$((line + 1)),$((line + 2))p" "$file" | grep -m1 -E '^[[:space:]]*#' |
|
||
sed -E 's/^[[:space:]]*#[[:space:]]*//')
|
||
fi
|
||
if [[ $descr == *" | "* ]]; then
|
||
if __das_uk; then descr=${descr%% | *}; else descr=${descr#* | }; fi
|
||
fi
|
||
printf '%-12s %s\n' "$name" "$descr"
|
||
done < <(compgen -A function | sort)
|
||
eval "$restore"
|
||
}
|
||
|
||
# --- archives --------------------------------------------------------------------
|
||
|
||
extract() {
|
||
# Розпакувати архіви (tar.*, zip, rar, 7z, gz, xz, zst, xbps…) | Extract archives (tar.*, zip, rar, 7z, gz, xz, zst, xbps…)
|
||
(($#)) || { __das_usage "extract АРХІВ..." "extract ARCHIVE..."; return; }
|
||
local f rc=0
|
||
for f in "$@"; do
|
||
if [[ ! -f $f ]]; then
|
||
__das_say "'$f' — не файл" "'$f' is not a file" >&2
|
||
rc=1
|
||
continue
|
||
fi
|
||
case ${f,,} in
|
||
*.tar | *.tar.* | *.tgz | *.tbz | *.tbz2 | *.txz | *.tzst | *.xbps) tar -xvf "$f" ;;
|
||
*.zip | *.jar) __das_need unzip unzip && unzip "$f" ;;
|
||
*.rar) __das_need unrar unrar && unrar x "$f" ;;
|
||
*.7z) __das_need 7z 7zip && 7z x "$f" ;;
|
||
*.gz) gunzip -k "$f" ;;
|
||
*.bz2) bunzip2 -k "$f" ;;
|
||
*.xz) unxz -k "$f" ;;
|
||
*.zst) __das_need unzstd zstd && unzstd "$f" ;;
|
||
*.lz4) __das_need lz4 lz4 && lz4 -d "$f" ;;
|
||
*.z) uncompress "$f" ;;
|
||
*)
|
||
__das_say "'$f': невідомий тип архіву" "'$f': unknown archive type" >&2
|
||
false
|
||
;;
|
||
esac || rc=1
|
||
done
|
||
return $rc
|
||
}
|
||
|
||
compress() {
|
||
# Запакувати файли в архів; тип за назвою | Pack files into an archive, the type comes from the name
|
||
(($# >= 2)) || { __das_usage "compress АРХІВ.tar.gz ФАЙЛ..." "compress ARCHIVE.tar.gz FILE..."; return; }
|
||
local archive=$1
|
||
shift
|
||
case ${archive,,} in
|
||
*.tar.gz | *.tgz) tar -czvf "$archive" -- "$@" ;;
|
||
*.tar.bz2 | *.tbz2) tar -cjvf "$archive" -- "$@" ;;
|
||
*.tar.xz | *.txz) tar -cJvf "$archive" -- "$@" ;;
|
||
*.tar.zst | *.tzst) __das_need zstd zstd && tar --zstd -cvf "$archive" -- "$@" ;;
|
||
*.tar) tar -cvf "$archive" -- "$@" ;;
|
||
*.zip) __das_need zip zip && zip -r "$archive" -- "$@" ;;
|
||
*.7z) __das_need 7z 7zip && 7z a "$archive" -- "$@" ;;
|
||
*)
|
||
__das_say "невідомий тип архіву: $archive (tar.gz, tar.xz, tar.zst, zip, 7z…)" \
|
||
"unknown archive type: $archive (tar.gz, tar.xz, tar.zst, zip, 7z…)" >&2
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
bu() {
|
||
# Резервна копія файлу з датою в назві | Back up a file with the date in its name
|
||
(($#)) || { __das_usage "bu ФАЙЛ..." "bu FILE..."; return; }
|
||
local f stamp
|
||
stamp=$(date +%Y%m%d%H%M%S)
|
||
for f in "$@"; do
|
||
cp -a -- "$f" "$f-$stamp.backup" && echo "$f-$stamp.backup"
|
||
done
|
||
}
|
||
|
||
# --- directories and files -----------------------------------------------------------
|
||
|
||
mkcd() {
|
||
# Створити каталог і перейти в нього | Make a directory and go into it
|
||
(($# == 1)) || { __das_usage "mkcd КАТАЛОГ" "mkcd DIR"; return; }
|
||
mkdir -p -- "$1" && cd -- "$1" || return
|
||
}
|
||
|
||
cl() {
|
||
# Перейти в каталог і показати його вміст | Change to a directory and list it
|
||
cd -- "${1:-$HOME}" && ls
|
||
}
|
||
|
||
up() {
|
||
# Піднятися на N каталогів вгору | Go N directories up
|
||
local n=${1:-1} dir=""
|
||
[[ $n =~ ^[0-9]+$ ]] || { __das_usage "up [N]" "up [N]"; return; }
|
||
while ((n-- > 0)); do dir+="../"; done
|
||
cd -- "${dir:-.}" || return
|
||
}
|
||
|
||
f() {
|
||
# Знайти файли за назвою (шаблон, без регістру) | Find files by name (a pattern, any case)
|
||
(($#)) || { __das_usage "f ШАБЛОН [КАТАЛОГ]" "f PATTERN [DIR]"; return; }
|
||
local pattern=$1
|
||
[[ $pattern == *[*?[]* ]] || pattern="*$pattern*"
|
||
find "${2:-.}" -iname "$pattern" 2>/dev/null
|
||
}
|
||
|
||
dirsize() {
|
||
# Розміри всього в каталозі, за зростанням | Sizes of everything in a directory, smallest first
|
||
local dir=${1:-.}
|
||
du -shx -- "$dir"/* "$dir"/.[!.]* 2>/dev/null | sort -h
|
||
}
|
||
|
||
whatsize() {
|
||
# Розміри підкаталогів (типово /) на тій самій ФС, за спаданням | Subdirectory sizes (/ by default) on the same filesystem, largest first
|
||
du -hx --max-depth=1 -- "${1:-/}" 2>/dev/null | sort -hr
|
||
}
|
||
|
||
bigfiles() {
|
||
# N найбільших файлів у каталозі | The N largest files in a directory
|
||
find "${1:-.}" -xdev -type f -printf '%s\t%p\n' 2>/dev/null | sort -nr | head -n "${2:-10}" |
|
||
numfmt --to=iec --field=1
|
||
}
|
||
|
||
ednt() {
|
||
# Редагувати файл, не змінюючи часу зміни | Edit a file keeping its modification time
|
||
(($# == 1)) || { __das_usage "ednt ФАЙЛ" "ednt FILE"; return; }
|
||
local mtime
|
||
mtime=$(stat -c %y -- "$1") || return
|
||
"${VISUAL:-${EDITOR:-vi}}" "$1"
|
||
touch -d "$mtime" -- "$1"
|
||
}
|
||
|
||
devid() {
|
||
# Пристрої: lsblk, blkid і lsusb разом | Devices: lsblk, blkid and lsusb at once
|
||
echo '--- lsblk ---'
|
||
lsblk -f -e 7
|
||
echo '--- blkid ---'
|
||
sudo blkid | grep -v '^/dev/loop'
|
||
echo '--- lsusb ---'
|
||
lsusb
|
||
}
|
||
|
||
# --- processes and history -------------------------------------------------------------
|
||
|
||
psa() {
|
||
# Процеси, в рядку яких є текст | Processes whose line contains the text
|
||
(($#)) || { __das_usage "psa ТЕКСТ" "psa TEXT"; return; }
|
||
ps aux | awk -v p="$1" 'NR == 1 || (tolower($0) ~ tolower(p) && !/awk -v p=/)'
|
||
}
|
||
|
||
pst() {
|
||
# Те саме деревом процесів | The same as a process tree
|
||
(($#)) || { __das_usage "pst ТЕКСТ" "pst TEXT"; return; }
|
||
# shellcheck disable=SC2009 # pgrep has no tree
|
||
ps auxf | grep -i -B 10 -A 5 -- "$1" | grep -v -e 'grep -i -B' -e 'ps auxf'
|
||
}
|
||
|
||
histat() {
|
||
# N найчастіших команд з історії | The N most used commands from history
|
||
history | awk -v n="${1:-10}" '
|
||
{ sub(/^ *[0-9]+\*? +/, ""); split($0, w, " "); c = w[1]; if (c != "") { cnt[c]++; total++ } }
|
||
END { for (c in cnt) printf "%d\t%.1f%%\t%s\n", cnt[c], cnt[c] * 100 / total, c }' |
|
||
sort -nr | head -n "${1:-10}" | nl
|
||
}
|
||
|
||
EC() {
|
||
# Код завершення попередньої команди | The previous command's exit code
|
||
local code=$?
|
||
printf '\e[1;33mcode %d\e[m\n' "$code"
|
||
return "$code"
|
||
}
|
||
|
||
please() {
|
||
# Повторити попередню команду через sudo | Run the previous command again with sudo
|
||
local cmd
|
||
cmd=$(fc -ln -1)
|
||
cmd=${cmd#"${cmd%%[![:space:]]*}"}
|
||
echo "sudo $cmd" >&2
|
||
sudo bash -c "$cmd"
|
||
}
|
||
|
||
# --- packages (xbps) --------------------------------------------------------------------
|
||
|
||
pkgshow() {
|
||
# Встановлені пакети, з фільтром за назвою | Installed packages, filtered by name
|
||
xbps-query -l | awk '{print $2}' | grep -i -- "${1:-.}" | sort
|
||
}
|
||
|
||
pkgfind() {
|
||
# Шукати пакети в репозиторіях | Search the repositories for packages
|
||
(($#)) || { __das_usage "pkgfind ТЕКСТ" "pkgfind TEXT"; return; }
|
||
xbps-query -Rs "$1"
|
||
}
|
||
|
||
pkgowner() {
|
||
# Якому пакету належить файл чи команда | Which package owns a file or a command
|
||
(($#)) || { __das_usage "pkgowner ФАЙЛ|КОМАНДА" "pkgowner FILE|COMMAND"; return; }
|
||
local path=$1
|
||
[[ $path == */* ]] || path=$(type -P -- "$1") || path=$1
|
||
xbps-query -o "$(readlink -f -- "$path")"
|
||
}
|
||
|
||
# --- network -----------------------------------------------------------------------------
|
||
|
||
myip() {
|
||
# Зовнішня IP-адреса | The public IP address
|
||
curl -fsS --max-time 5 https://ifconfig.me/ip && echo
|
||
}
|
||
|
||
netinfo() {
|
||
# Адреси, шлюз, DNS і зовнішня IP | Addresses, gateway, DNS and the public IP
|
||
echo "--- $(__das_say "Адреси" "Addresses") ---"
|
||
command ip -br address | grep -v '^lo '
|
||
echo "--- $(__das_say "Шлюз" "Gateway") ---"
|
||
command ip route show default
|
||
echo "--- DNS ---"
|
||
awk '/^nameserver/ {print $2}' /etc/resolv.conf
|
||
echo "--- $(__das_say "Зовнішня IP" "Public IP") ---"
|
||
myip
|
||
}
|
||
|
||
listen() {
|
||
# Слухати порт TCP і показувати, що прийшло | Listen on a TCP port and print what arrives
|
||
(($# == 1)) || { __das_usage "listen ПОРТ" "listen PORT"; return; }
|
||
__das_need nc openbsd-netcat || return
|
||
nc -lvn "$1"
|
||
}
|
||
|
||
rmhost() {
|
||
# Прибрати хост або рядок N з ~/.ssh/known_hosts | Remove a host or line N from ~/.ssh/known_hosts
|
||
(($# == 1)) || { __das_usage "rmhost ХОСТ|НОМЕР_РЯДКА" "rmhost HOST|LINE_NUMBER"; return; }
|
||
if [[ $1 =~ ^[0-9]+$ ]]; then
|
||
sed -i.bak "${1}d" ~/.ssh/known_hosts && echo "$HOME/.ssh/known_hosts.bak"
|
||
else
|
||
ssh-keygen -R "$1"
|
||
fi
|
||
}
|
||
|
||
serve() {
|
||
# Роздати поточний каталог по HTTP (типово порт 8000) | Serve the current directory over HTTP (port 8000 by default)
|
||
__das_need python3 python3 || return
|
||
python3 -m http.server "${1:-8000}"
|
||
}
|
||
|
||
weather() {
|
||
# Погода з wttr.in (місто з аргументу або за IP) | Weather from wttr.in (the city given, or by IP)
|
||
local lang=en
|
||
__das_uk && lang=uk
|
||
curl -fsS --max-time 10 "https://wttr.in/${1// /+}?lang=$lang"
|
||
}
|
||
|
||
cheat() {
|
||
# Шпаргалка до команди з cheat.sh | A command's cheat sheet from cheat.sh
|
||
(($#)) || { __das_usage "cheat КОМАНДА" "cheat COMMAND"; return; }
|
||
curl -fsS --max-time 10 "https://cheat.sh/$1"
|
||
}
|
||
|
||
# --- small tools -------------------------------------------------------------------------
|
||
|
||
calc() {
|
||
# Порахувати вираз | Calculate an expression
|
||
(($#)) || { __das_usage "calc ВИРАЗ" "calc EXPRESSION"; return; }
|
||
if command -v bc >/dev/null; then
|
||
echo "scale=3; $*" | bc -l
|
||
else
|
||
awk "BEGIN { print $* }"
|
||
fi
|
||
}
|
||
|
||
epoch() {
|
||
# Поточний час Unix або перетворити його на дату | The current Unix time, or turn one into a date
|
||
if (($#)); then
|
||
date -d "@$1" -R
|
||
else
|
||
date +%s
|
||
fi
|
||
}
|
||
|
||
genpass() {
|
||
# Випадковий пароль (типово 20 символів) | A random password (20 characters by default)
|
||
local n=${1:-20}
|
||
[[ $n =~ ^[0-9]+$ ]] || { __das_usage "genpass [ДОВЖИНА]" "genpass [LENGTH]"; return; }
|
||
LC_ALL=C tr -dc 'A-Za-z0-9_@%+=.,-' </dev/urandom | head -c "$n"
|
||
echo
|
||
}
|
||
|
||
csrc() {
|
||
# Скомпілювати й запустити програму на C | Compile and run a C source file
|
||
(($#)) || { __das_usage "csrc ФАЙЛ.c [АРГУМЕНТИ]" "csrc FILE.c [ARGS]"; return; }
|
||
[[ -r $1 ]] || { __das_say "немає файлу $1" "no such file: $1" >&2; return 1; }
|
||
__das_need gcc gcc || return
|
||
local src=$1 bin rc
|
||
shift
|
||
bin=$(mktemp) || return
|
||
gcc "$src" -o "$bin" && "$bin" "$@"
|
||
rc=$?
|
||
rm -f -- "$bin"
|
||
return $rc
|
||
}
|
||
|
||
note() {
|
||
# Нотатки в ~/.notes: без аргументів показати, -c очистити | Notes in ~/.notes: no arguments shows them, -c clears
|
||
local file=$HOME/.notes
|
||
if ! (($#)); then
|
||
[[ -f $file ]] && cat -- "$file"
|
||
elif [[ $1 == -c ]]; then
|
||
: >"$file"
|
||
else
|
||
printf '%s\n' "$*" >>"$file"
|
||
fi
|
||
}
|
||
|
||
todo() {
|
||
# Справи в ~/.todo: -l з номерами, -r N виконано, -c очистити | To-dos in ~/.todo: -l numbered, -r N done, -c clears
|
||
local file=$HOME/.todo
|
||
case ${1:-} in
|
||
"") [[ -f $file ]] && cat -- "$file" ;;
|
||
-l) [[ -f $file ]] && nl -b a -- "$file" ;;
|
||
-c) : >"$file" ;;
|
||
-r)
|
||
local n=${2:-}
|
||
if [[ -z $n ]]; then
|
||
nl -b a -- "$file"
|
||
read -r -p "$(__das_say "Номер, що прибрати: " "Number to remove: ")" n
|
||
fi
|
||
[[ $n =~ ^[0-9]+$ ]] || return 1
|
||
sed -i "${n}d" -- "$file"
|
||
;;
|
||
*) printf '%s\n' "$*" >>"$file" ;;
|
||
esac
|
||
}
|
||
|
||
man() {
|
||
# man з кольоровими заголовками | man with coloured headings
|
||
LESS_TERMCAP_mb=$'\e[1;31m' LESS_TERMCAP_md=$'\e[1;31m' LESS_TERMCAP_me=$'\e[0m' \
|
||
LESS_TERMCAP_so=$'\e[1;44;33m' LESS_TERMCAP_se=$'\e[0m' \
|
||
LESS_TERMCAP_us=$'\e[1;32m' LESS_TERMCAP_ue=$'\e[0m' \
|
||
GROFF_NO_SGR=1 command man "$@"
|
||
}
|