# 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_@%+=.,-' &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 "$@" }