From bb07073c6a9f3b9d5c04a759ef186421d013aafc Mon Sep 17 00:00:00 2001 From: ge Date: Wed, 27 Jul 2022 08:31:34 +0300 Subject: [PATCH] init --- .gitignore | 1 + README | 25 +++ UNLICENSE | 24 +++ src/appimage | 245 +++++++++++++++++++++++++++++ src/btw | 187 +++++++++++++++++++++++ src/codes | 94 ++++++++++++ src/currency | 138 +++++++++++++++++ src/dots | 174 +++++++++++++++++++++ src/git-mass-clone | 88 +++++++++++ src/github-starred-repos | 22 +++ src/http | 110 +++++++++++++ src/log | 95 ++++++++++++ src/md2 | 157 +++++++++++++++++++ src/mega | 121 +++++++++++++++ src/parts | 197 ++++++++++++++++++++++++ src/rand | 47 ++++++ src/safeeject | 113 ++++++++++++++ src/unflac | 31 ++++ src/upload | 323 +++++++++++++++++++++++++++++++++++++++ src/view | 70 +++++++++ src/yad | 89 +++++++++++ 21 files changed, 2351 insertions(+) create mode 100644 .gitignore create mode 100644 README create mode 100644 UNLICENSE create mode 100755 src/appimage create mode 100755 src/btw create mode 100755 src/codes create mode 100755 src/currency create mode 100755 src/dots create mode 100755 src/git-mass-clone create mode 100755 src/github-starred-repos create mode 100755 src/http create mode 100755 src/log create mode 100755 src/md2 create mode 100755 src/mega create mode 100755 src/parts create mode 100755 src/rand create mode 100755 src/safeeject create mode 100755 src/unflac create mode 100755 src/upload create mode 100755 src/view create mode 100755 src/yad diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6da5dd --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +untracked/ diff --git a/README b/README new file mode 100644 index 0000000..d703279 --- /dev/null +++ b/README @@ -0,0 +1,25 @@ +A lot of Shell scripts. + +Scripts +------- + +appimage AppImages manager. +btw Track battery and send notify if low battery. +codes Print ANSI sequences (16, 256 colors and formatting). +currency Get currencies from Russian Central Bank and calculate. +dots Dotfiles manager. Save and push selected files to git repo. +github-starred-repos Get list of user's starred repos from GitHub. +git-mass-clone Mass clone or pull repos from file. +http Python 3 http.server runner. +log Logs viewing tool (nginx and exim4). Prints logs to STDOUT. +md2 Convert Markdown to HTML, ROFF and PDF. Pandoc wrapper. +mega MEGAcmd wrapper. +parts Store and view code snippets. +rand Print random array item. +safeeject Safely remove an external drive. Useful for USB-devices. +unflac Convert FLAC to MP3. +upload Upload files to remote server via server alias. +view Print highlighted text to STDOUT. highlight wrapper. +yad Download file from Yandex.Disk storage. + +Most scripts have an builtin help text. Try `%scriptname% --help`. diff --git a/UNLICENSE b/UNLICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/src/appimage b/src/appimage new file mode 100755 index 0000000..2b4c48f --- /dev/null +++ b/src/appimage @@ -0,0 +1,245 @@ +#!/usr/bin/env bash +# +# * appimage - Manage AppImages on system. +# +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# For more information, please refer to + +set -o errexit +cache_dir="$HOME/.cache/appimage" +appimages_info="$HOME/.config/appimages.info" +PREFIX="${PREFIX:-$HOME/.local}" +ASSUME_YES="${ASSUME_YES:-}" + +print_help() { +cat <<- EOF +Manage AppImages. + +Usage: $0 [options] [] + +Options: + -i, --install install AppImage from URL or from FILE. + -d, --no-desktop don't create a desktop file. See https://www.freedesktop.org/wiki/Specifications/desktop-entry-spec/ + -p, --prefix installation prefix [default: $PREFIX] + -r, --remove remove AppImage. + -l, --list list installed AppImages. + -s, --show show AppImage related info. + -y, --yes automatic yes to prompts. + -h, --help print this help message and exit. + -v, --version print version and exit. + +Environment: + PREFIX default installation prefix. + ASSUME_YES automatic yes to prompts. +EOF +} + +yesno() { + # Yes/No interactive dialog. + # + # Usage: if yesno 'Question'; then ... + local answer= + [ "$ASSUME_YES" ] && return 0 + + while [ ! "$answer" ]; do + echo -en "$* (y/n) " + read -r reply + case "${reply,,}" in + y|yes) answer=0;; + n|no) answer=1;; + *) echo "Please, answer y or n";; + esac + done + return "$answer" +} + +# https://stackoverflow.com/a/49197786 +extract() { + tar -xOvaf "$1" 2>/dev/null > "$2" && return 0 + case $(file "$1") in + *bzip2*) bzip2 -dkc "$1" > "$2" ;; + *gzip*) gunzip -c "$1" > "$2" ;; + *'7-zip'*) 7z e -so "$1" > "$2" ;; + *zip*) ;& + *Zip*) unzip -p "$1" > "$2" ;; + *xz*) ;& + *XZ*) unxz -c "$1" > "$2" ;; + *) return 1;; + esac +} + +install_appimage() { + local image desktop_file name file version description desktop + + if [ -f "$1" ]; then + image="$1" + elif [[ "$1" =~ https?://.+ ]]; then + echo -e "\e[1m==> Download AppImage ...\e[0m" + mkdir -p "$cache_dir" + image="$cache_dir/temporary.AppImage" + wget -c "$1" -O "$image" + else + echo No such file: "$1" >&2; exit 1 + fi + + local uncompressed_image="$cache_dir/temporary.AppImage.uncompressed" + if extract "$image" "$uncompressed_image"; then + image="$uncompressed_image" + fi + + # Check file type + if [[ ! "$(file "$image")" =~ .*ELF.* ]]; then + echo File is not correct Linux binary. >&2; exit 1 + fi + + chmod +x "$image" + + echo -e "\e[1m==> Extracting data ...\e[0m" + + desktop_file="$("$image" --appimage-extract '*.desktop')" + name="$(grep -Po '(?<=Name=)(.*)' "$desktop_file")" + version="$(grep -Po '(?<=X-AppImage-Version=)(.*)' "$desktop_file")" + description="$(grep -Po '(?<=Comment=)(.*)' "$desktop_file")" + file="$PREFIX/bin/${name}-${version}.AppImage" + + echo -e "\e[1m==> Install ...\e[0m" + + # Install files + install -Dm755 -D "$image" "$file" + if [ -n "$no_desktop" ]; then + desktop=None + else + desktop="$PREFIX/share/applications/${name}-${version}.desktop" + install -Dm644 -D "$desktop_file" "$desktop" + sed '/Exec=/d' -i "$desktop"; echo "Exec=$file" >> "$desktop" + rm "${desktop_file:-/nonexistent}" + rmdir "$PWD/squashfs-root" + fi + + # Clean cache + [ -f "$cache_dir/temporary.AppImage" ] && rm "$cache_dir/temporary.AppImage" + [ -f "$cache_dir/temporary.AppImage.uncompressed" ] && rm "$cache_dir/temporary.AppImage.uncompressed" + + # Save App data + printf "%s:%s:%s:%s:%s\n" "$name" "$version" "$description" "$file" "$desktop" >> "$appimages_info" + echo Done. + + echo -e "\e[1mRecap:\e[0m" + echo APPIMAGE: "$name" + echo VERSION: "$version" + echo EXECUTABLE: "$file" + echo DESKTOP FILE: "$desktop" + + if [[ ! "$PATH" =~ "$PREFIX" ]]; then + echo + echo PREFIX is not in PATH! + echo Add this line to your \~/.bashrc file: + echo + echo export PATH="$PREFIX:$PATH" + echo + echo and restart your Shell session. + fi +} + +remove_appimage() { + local appimage _appimage name version file desktop + appimage="$1" + + if [ -f "$appimages_info" ]; then + if awk -F: '{print $1 ":" $2}' "$appimages_info" | grep -i "^${appimage}\$" >/dev/null; then + _appimage="$(grep -i "$appimage" "$appimages_info")" + name="$(awk -F: '{print $1}' <<< "$_appimage")" + version="$(awk -F: '{print $2}' <<< "$_appimage")" + file="$(awk -F: '{print $4}' <<< "$_appimage")" + desktop="$(awk -F: '{print $5}' <<< "$_appimage")" + + echo AppImage: "$name" + echo Version: "$version" + echo Remove: + echo "$file" + echo "$desktop" + if yesno 'Are you sure?'; then + [ -f "$file" ] && rm "$file" + [ -f "$desktop" ] && rm "$desktop" + sed -e "/$appimage/Id" -i "$appimages_info" # Remove App data + echo "$appimage" removed. + else + echo Abort + fi + else + echo AppImage not found: "$appimage" + echo Note: Specify AppImage name and version separated by a colon e.g. SomeApp:1.8.2, case insensitive. + fi + else + echo No AppImages installed. >&2; exit 1 + fi +} + +list_appimages() { + # shellcheck disable=SC2002 + cat "$appimages_info" 2>/dev/null | awk -F: \ + 'BEGIN {print "NAME:VERSION:DESCRIPTION"} {print $1 ":" $2 ":" $3}' | column -t -s : +} + +show_appimage() { + cat "$appimages_info" 2>/dev/null | grep -i -m 1 "$1" | + awk -F: '{print "APPIMAGE: " $1 "\nVERSION: " $2 "\nDESCRIPTION: " $3 "\nEXECUTABLE: " $4 "\nDESKTOP FILE: " $5}' +} + +# Print help if no arguments passed +[[ "$#" == 0 ]] && { print_help; exit 1; } + +# Transform long options to short ones +for arg in "$@"; do + shift + case "$arg" in + --install) set -- "$@" "-i";; + --no-desktop) set -- "$@" "-d";; + --prefix) set -- "$@" "-p";; + --remove) set -- "$@" "-r";; + --list) set -- "$@" "-l";; + --show) set -- "$@" "-s";; + --yes) set -- "$@" "-y";; + --help) set -- "$@" "-h";; + --version) set -- "$@" "-v";; + *) set -- "$@" "$arg";; + esac +done + +# Parse short options +while getopts ":i:dp:r:ls:yhv" opt; do + case "$opt" in + i) install_appimage "$OPTARG"; exit "$?";; + d) no_desktop='yes';; + p) PREFIX="$OPTARG"; ;; + r) remove_appimage "$OPTARG"; exit "$?";; + l) list_appimages; exit $?;; + s) show_appimage "$OPTARG"; exit "$?";; + y) ASSUME_YES=1;; + h) print_help; exit 0;; + v) echo 0.2.0; exit 0;; + *) echo "Unknown option $opt" >&2; exit 1;; + esac +done diff --git a/src/btw b/src/btw new file mode 100755 index 0000000..4ae21c9 --- /dev/null +++ b/src/btw @@ -0,0 +1,187 @@ +#!/usr/bin/env bash +# +# * Battery Watchdog (see help text below). +# +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# For more information, please refer to +# +# ### +# +# btw depends these packages: +# `acpi` ; +# `libnotify` ; +# +# Installation: +# Arch Linux: +# # pacman -S acpi (commynity repo) +# # pacman -S libnotyfy (extra repo) +# Debian/Ubuntu: +# # apt-get install acpi +# # apt-get install libnotify + +btw_ver() { + echo btw 1.0; exit 0 +} + +btw_help() { + cat <<- 'EOF' +btw - Battery Watchdog. Send notification if critical battery level reached. + +Usage: btw [-v|--version] [-h|--help] [-c|--crit ] + [-p|--preriod ] [-l|--log ] + [-s|--summary ] [-b|--body ] [-w|--watch] + +Options: + -v, --version print version and exit. + -h, --help print this help message and exit. + -c, --crit battery critical value (in percents), default: 10 + -p, --preriod battery check out period in seconds, default: 30 + -l, --log log file, default: ~/.cache/btwatchdog.log + -s, --summary custom notification title text. + -b, --body custom notification body text. + -w, --watch follow to log file ('tail -f'), use '--log' to + set log file path. + +Run watchdog in background. See application autostart options for your desktop +environment. For example: + +Add to your '~/.xinitrc': + btw & + +or create '~/.config/autostart/btw.desktop' if you have GNOME Desktop: + [Desktop Entry] + Name=Laptop battery watchdog (btw) + Exec=btw + Type=Application + +Logging + +btw writes battery status log in log file. Set file by '--log' option. +Log format: '[date time] [state] [level]%' +There is: [date time] `date +'%Y-%m-%d %H:%M:%S'` command output. + [state] battery status, can be 'Charging' or 'Discharging'. + [level] battery current level in percents. + +Notifications + +btw will send notification at every battery checkout while current battery +level in lower than critical value. For example, with command: + btw --crit 10 --period 30 +you will recieve notification every 30 seconds until battery is under 10%. + +You can set up custom notification text via '--summary' and '--body' options. +Variables can be used in notification: + level current battery level in percents. + state current battery status. +For example: + btw --summary 'Battery is very low!' --body 'Status: ${state,,}, ${level}%' + +License: The Unlicense +EOF + exit 0 +} + +# Default values. +period=30 # check battery every `period` seconds +critical=10 # battery limit (in %) +clevel=100 # current battery value (in %, temporary) +summary='Extremely low battery: ${level}%' +body='Check the charger, currently is ${state,,}' + +lock=/tmp/btwatchdog.lock # lock file +log=$HOME/.cache/btwatchdog.log # log +mkdir -p $HOME/.cache || { echo "Cannot write log file: $HOME/.cache" >&2; } + +btw_notify() { + notify-send --urgency=critical --icon=battery-empty --category=System \ + "$(eval echo "$summary")" "$(eval echo "$body")" +} + +btw_isdigit() { + if [[ "$1" =~ [0-9]+ ]]; then + return 0 + else + echo 'Value is not integer!' >&2; exit 1 + fi +} + +# Transform long options to short ones +for arg in "$@"; do + shift + case "$arg" in + --crit) set -- "$@" "-c" ;; + --period) set -- "$@" "-p" ;; + --log) set -- "$@" "-l" ;; + --summary) set -- "$@" "-s" ;; + --body) set -- "$@" "-b" ;; + --watch) set -- "$@" "-w" ;; + --help) set -- "$@" "-h" ;; + --version) set -- "$@" "-v" ;; + *) set -- "$@" "$arg";; + esac +done + +# Parse short opts +while getopts c:p:l:s:b:whv opt; do + case "$opt" in + c) btw_isdigit "$OPTARG"; critical="$OPTARG";; + p) btw_isdigit "$OPTARG"; period="$OPTARG" ;; + l) log="$OPTARG";; + s) summary="$OPTARG";; + b) body="$OPTARG";; + w) watchdog=1;; + h) btw_help;; + v) btw_ver;; + esac +done + +if [ "$watchdog" ]; then + tail --follow "$log" + exit "$?" +fi + +while true; do + battery=$(acpi -b | awk '{print $3 " " $4}' | sed -E 's/,|%//g;') + level=${battery##* } # e.g. "49" (in percents) + state=${battery%% *} # e.g. "Charging" + + if [ "$level" -lt "$critical" ]; then + if [ ! -f "$lock" ]; then + btw_notify + touch "$lock" + fi + # Warn in every percent less + if [ "$level" -lt "$clevel" ]; then + btw_notify + touch "$lock" + clevel="$level" + fi + else + [ -f "$lock" ] && rm "$lock" + fi + + echo "$(date +'%Y-%m-%d %H:%M:%S') ${state,,} ${level}%" >> "$log" + sleep $period +done diff --git a/src/codes b/src/codes new file mode 100755 index 0000000..3affccb --- /dev/null +++ b/src/codes @@ -0,0 +1,94 @@ +#!/usr/bin/env bash + +codes_help() { + cat <<- EOF +Display ANSI escape sequences. + +Usage: codes [-vha] [-f] [-16] [-256] + +Options: + -a, --all display all codes (default). + -f, --format display text formatting sequences table. + -16 display 8/16 ANSI color codes table. + -256 display 88/256 ANSI color codes table. + -h, --help print this help message and exit. + -v, --version print version and license info and exit. + +NOTE: Safe codes for Linux TTY and most other terminals are 8 colors, +inverted color and bold. Support for other sequences depends on the terminal +being used. +EOF + codes_ansi_helper + exit 0 +} + +codes_version() { echo codes v1.0.1; exit 0; } + +codes_ansi_helper() { + echo + echo -e ' character can be set as \\e or \\033 or \\x1B' + echo + echo -e '16 colors and formatting control sequence is [ColorNumberm' + echo -e '256 foreground control sequence is [38;5;ColorNumberm' + echo -e '256 background control sequence is [48;5;1mColorNumberm' + echo + echo -e '[0m -- reset all attributes.' +} + +codes_ansi_format() { + echo -e 'Set Reset' + echo -e " 1 21 \e[1mBold\e[0m"; + echo -e " 2 22 \e[2mDim\e[0m" + echo -e " 3 23 \e[3mItalic\e[0m" + echo -e " 4 24 \e[4mUnderlined\e[0m" + echo -e " 5 25 \e[5mBlinking\e[0m" + echo -e " 7 27 \e[7mInverted\e[0m" + echo -e " 8 28 \e[8mHidden \e[0m(hidden)" + echo -e " 9 29 \e[9mStrike\e[0m" +} + +codes_ansi_16() { + for fgbg in 3 9 4 10; do + for color in {0..7}; do + printf "\e[${fgbg}%sm %3s \e[0m" $color ${fgbg}${color} + if [ $color == 7 ]; then echo -e ''; fi; + done + done +} + +codes_ansi_256() { + for fgbg in 38 48 ; do # Foreground / Background + for color in {0..255} ; do # Colors + # Display the color + printf "\e[${fgbg};5;%sm %3s \e[0m" $color $color + # Display 6 colors per lines + if [ $((($color + 1) % 6)) == 4 ] ; then + echo # New line + fi + done + done +} + +codes_ansi_all() { + echo -e '\e[1mFORMATTING\e[0m' + codes_ansi_format + echo -e '\n\e[1m8/16 COLORS\e[0m' + codes_ansi_16 + echo -e '\n\e[1m88/256 COLORS\e[0m' + codes_ansi_256 + codes_ansi_helper +} + +[[ "$@" ]] || codes_ansi_all + +while (( "$#" )); do +case "$1" in + -a|--all) codes_ansi_all; exit 0;; + -f|--format) codes_ansi_format; exit 0;; + -16|--16) codes_ansi_16; exit 0;; + -256|--256) codes_ansi_256; exit 0;; + -h|--help) codes_help;; + -v|--version) codes_version;; + *) echo "${0##/*}: $1: bad option" >&2; exit 1;; +esac +done diff --git a/src/currency b/src/currency new file mode 100755 index 0000000..67a49ec --- /dev/null +++ b/src/currency @@ -0,0 +1,138 @@ +#!/usr/bin/env bash + +# Favourites +curr='USD|EUR|GBP|JPY|UAH' + +# Get HTML-table with currencies in RUB from Russian Central Bank page. +full_table="$(curl -sSL https://www.cbr.ru/currency_base/daily/ | + sed "/<\/table>/,\$d" | tac | sed "/\s*<%><%g' | sed 's%%%g' | + sed 's%%\n%g;s%%%g' | sed 's%/%%g' | + awk -F '
' '{print $2 " | " $4 " | " $6 " | " $8 " | " $10}')" + +all_currencies_long() { + echo "$full_table" | + awk 'BEGIN {print "Цифр. код | Букв. код | Ед. | Валюта | Курс"} + {print $0}' | column -t -s '|' | sed '2,3d' +} + +all_currencies() { + echo "$full_table" | + awk -F '|' '{print $2 " " $3 " " $5}' | column -t +} + +is_digit() { + if [[ "$1" =~ [0-9]+ ]]; then + return 0 + else + return 1 + fi +} + +is_currency() { + if grep "^${1^^}$" <<< "$(all_currencies | awk '{print $1}')" > /dev/null; then + return 0 + else + return 1 + fi +} + +available_currencies() { + all_currencies | cut -d ' ' -f 1 | sort | tr '\n' ' ' +} + +calculate_rub() { + _currency="$(all_currencies | grep "$currency" | awk '{print $3}' | sed 's%,%.%g')" + _number="$(all_currencies | grep "$currency" | awk '{print $2}')" + echo "(${_currency}/${_number})*${number}" | bc -l +} + +while getopts iIlahv OPT; do + case "$OPT" in + i) noninteractive=1;; + I) interactive=1;; + l) long=1;; + a) all=1;; + h) echo -e 'Get currencies from Russian Central Bank and calculate.\n\n' \ + '\bUsage: currency [-iIlahv] [] []\n' \ + '\bOptions:\n' \ + '\b -i non-interactive mode.\n' \ + '\b -I interactive mode.\n' \ + '\b -l print currencies in long format.\n' \ + '\b -a print all currencies.\n' \ + '\b -h print this help message and exit..\n' \ + '\b -v print version and exit..\n\n' \ + "\bAvailable currencies: $(available_currencies)" + exit 0;; + v) echo 'currency 1.1'; exit 0;; + esac +done + +# Parse positional arguments. +shift $((OPTIND-1)) + +while (( "$#" )); do + if is_currency "$1"; then + currency="${1^^}" + elif is_digit "$1"; then + number="$1" + else + echo Invalid argument $1 >&2; exit 1 + fi + shift +done + +if [ "$currency" ] && [ "$number" ]; then + calculate_rub + exit "$?" +fi + +if [ ! "$interactive" ]; then + # Show currencies table + if [ "$long" ]; then + all_currencies_long | head -n 1 + if [ "$all" ]; then + all_currencies_long | sed '1d' + else + all_currencies_long | grep -E "$curr" + fi + else + if [ "$all" ]; then + all_currencies + else + all_currencies | grep -E "$curr" + fi + fi + + if [ "$noninteractive" ]; then + exit 0 + else + echo + fi +fi + +# Interactive mode +echo 'Specify currency and c.u. (^C for exit):' +while true; do + if [ "$currency" ]; then + echo -n ">>> (${currency^^}) " + read number + if is_currency "$number"; then + currency="${number^^}"; unset number + elif ! is_digit "$number"; then + echo Invalid number $number; unset number + fi + else + echo -n ">>> " + read currency + if is_currency "$currency"; then + currency="${currency^^}" + else + echo Invalid currency $currency; unset currency + fi + fi + + if [ "$number" ]; then + calculate_rub + fi +done diff --git a/src/dots b/src/dots new file mode 100755 index 0000000..a48498e --- /dev/null +++ b/src/dots @@ -0,0 +1,174 @@ +#!/usr/bin/env bash + +version=0.3 +dots_conf_file=${HOME}/.dots.conf +dots_local_repo=${HOME}/.local/share/dots +unwanted_files=( .netrwhist ) + +# Color scheme +R="\e[31m" # red +G="\e[32m" # green +Y="\e[33m" # yellow +B="\e[94m" # light blue +M="\e[95m" # light magenta +C="\e[36m" # cyan +Gr="\e[37m" # grey +N="\e[0m" # no color +b="\e[1m" # bold font + +dots_version() { + echo dots $version; exit 0 +} + +dots_help() { + cat <<- EOF +Save and push dotfiles. + +Usage: dots [-vh] command + +Commands: + save copy dotfiles to local repo (${dots_local_repo//$HOME\//\~\/}) + push commit and push dotfiles to remote git repo. + show run 'git status' and print information about local repo. + diff run 'git diff' in local repo. + +Options: + -v, --version print version and exit. + -h, --help print this message and exit. +EOF + exit 0 +} + +dots_init() { + if [ -f "$dots_conf_file" ]; then + : # do nothing + else + echo -e "${R}${dots_conf_file} doesn't exist\n" >&2 + echo -e "${dots_conf_file} content example:" >&2 + echo " .bashrc" >&2 + echo " .bash_aliases" >&2 + echo -e "${N}" + exit 1 + fi + + if [ -d "${dots_local_repo}/.git" ]; then + : # do nothing + else + echo -e "${b}Init new ropository in${N} ${dots_local_repo}" + mkdir -p "$dots_local_repo" + echo -e "${B}; git init${N}" + git init "$dots_local_repo" + fi +} + +dots_clean() { + # Remove confidential data from .bash* files. + # `# Work` comment is trigger. Everything after this + # comment to end of file will be deleted. + + echo -e "\nClean up restricted data from ${b}.bash*${N} files ..." + for bashfile in `find ${dots_local_repo} -type f -name ".bash*"`; do + lnnum=`cat "$bashfile" | grep '# Work' -n | cut -d ':' -f 1` + if grep '# Work' "$bashfile" > /dev/null; then + echo "--> $bashfile" + fi + #echo "LN: $lnnum" + if [ "$lnnum" != "" ]; then + sed -i "${lnnum}q" "$bashfile" + fi + done +} + +dots_remove_unwanted() { + echo -e "${b}Remove unwanted files ...${N}" + echo -e "${B}; find $dots_local_repo -name {...} -delete -print${N}" + for unwanted in "${unwanted_files[@]}"; do + find "$dots_local_repo" -name "$unwanted" -delete -print + done +} + +dots_save() { + dots_init + + echo -e "${b}Copy dotfiles to repo${N}..." + echo -e "${B}; cp --parents --recursive {...} ${dots_local_repo}${N}" + for dotfile in $(cat "$dots_conf_file"); do + cp --parents --recursive "${HOME}/${dotfile}" "${dots_local_repo}/" + #echo -e "${G}${dotfile}${N}" + done + + dots_remove_unwanted # Remove unwanted files from local repo + + echo -e "${B}; tree -a -F -I .git${N}" + tree -a -F -I .git "$dots_local_repo" + + dots_clean # Remove confidential data from local repo + + echo -e "\nDone!" + + echo -e "\n${b}Tips:${N}" \ + "\n Use ${M}${b}dots show${N} to run ${C}'git status'${N}" \ + "or ${M}${b}dots diff${N} to run ${C}'git diff'${N} in local repo." \ + "\n Use ${M}${b}dots push${N} to commit and push to remote repo." +} + +dots_push() { + echo -e "${b}---${N}" + echo -e "${B}; git add -A${N}" + git -C "${dots_local_repo}" add -A + echo -e "${B}; git commit -m 'Update dotfiles'${N}" + git -C "${dots_local_repo}" commit -m 'Update dotfiles' + + local remote="$(git -C "${dots_local_repo}" remote show)" + if [ "$remote" == "" ]; then + echo -e "${b}Add new remote server:${N}" + echo -e \ + "Paste address like: ${Gr}git@github.com:user/repo.git${N} below:" + while true; do + read remote_addr + if [[ "$remote_addr" =~ .*/.*\.git ]]; then + break + else + echo "Please, re-enter remote address:" + fi + done + echo -en "${B}; git remote add dots-repo ${remote_addr}${N} ... " + git -C "${dots_local_repo}" remote add dots-repo "$remote_addr" + echo -e "${G}${b}OK${N}" + else + : # do nothing + fi + + echo -e "${b}Processing${N}..." + echo -e "${B}; git push dots-repo master${N}" + git -C "${dots_local_repo}" push -u dots-repo master +} + +dots_show() { + echo -e "${b}Config:${N} $dots_conf_file" + echo -e "${b}Local repo:${N} $dots_local_repo" + echo -e "${B}; git status${N}" + git -C "$dots_local_repo" status +} + +dots_diff() { + echo -e "${b}Config:${N} $dots_conf_file" + echo -e "${b}Local repo:${N} $dots_local_repo" + echo -e "${B}; git diff${N}" + git -C "$dots_local_repo" diff "$@" +} + +[[ ! "$@" ]] && dots_help + +while (( "$#" )); do + case "$1" in + save) shift; dots_save "$@"; shift "$#";; + push) shift; dots_push "$@"; shift "$#";; + show) shift; dots_show "$@"; shift "$#";; + diff) shift; dots_diff "$@"; shift "$#";; + -v|--version) dots_version ;; + -h|--help) dots_help ;; + -*) echo "$1: bad option" >&2; exit 1;; + *) args+=("$1"); shift;; + esac +done diff --git a/src/git-mass-clone b/src/git-mass-clone new file mode 100755 index 0000000..c9c1e22 --- /dev/null +++ b/src/git-mass-clone @@ -0,0 +1,88 @@ +#!/usr/bin/env bash + +print_help() { + cat <<- EOF +Clone and pull remote git repositories. + +Usage: $0 [-fchv] + +Options: + -f, --fetch-from clone repositories listed in file. + -c, --chdir change working directory [default: current] + -h, --help print this help message and exit. + -v, --version print version and exit. + +Example of repository list file: + +https://github.com/user/repo.git # my nice repo +https://github.com/anothr_user/repo.git + +Comments is allowed. Use hash (#) sign for comments. +EOF + exit 0 +} + +[[ "$@" ]] || print_help + +# Transform long options to short ones +for arg in "$@"; do + shift + case "$arg" in + --fetch-from) set -- "$@" "-f";; + --chdir) set -- "$@" "-c";; + --help) set -- "$@" "-h";; + --version) set -- "$@" "-v";; + *) set -- "$@" "$arg";; + esac +done + +while getopts ":f:c:hv" opt; do + case "$opt" in + f) fetch_from="$OPTARG";; + c) chdir="$OPTARG";; + h) print_help;; + v) echo 0.1; exit 0;; + *) echo "Unknown option $opt" >&2; exit 1;; + esac +done + +if [ "$fetch_from" ]; then + if test -f "$fetch_from"; then + fetch_from="$(realpath "$fetch_from")" + else + echo "No such file $fetch_from" >&2; exit 1 + fi +else + echo "Missing argument for --fetch-from" >&2; exit 1 +fi + +if [ "$chdir" ]; then + chdir="$(realpath "$chdir")" +else + chdir="$PWD" +fi + +if [ -d "$chdir" ]; then + : +else + echo "No such directory $chdir" >&2; exit 1 +fi + +echo -e "Fetching git repos from $fetch_from to $chdir ..." +echo -n 'Date: '; date -R +git --version + +sed 's/#.*//g;/^$/d' "$fetch_from" | while read repo; do + echo -e "\nFETCHING $repo ..." + echo '# ---------------------------------------------------------' + repo_dir="${repo##*/}" + repo_dir="${repo_dir//\.git}" + repo_dir="$(realpath "${chdir}/${repo_dir}")" + if git -C "$chdir" clone "$repo"; then + : + else + echo "ENTERING INTO $repo_dir ..." + cd "$repo_dir" && git pull && cd - >/dev/null || + { echo "Cannot change dir to $OLDPWD" >&2; exit 1; } + fi +done diff --git a/src/github-starred-repos b/src/github-starred-repos new file mode 100755 index 0000000..248ef60 --- /dev/null +++ b/src/github-starred-repos @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# Get user starred repos list. + +# GitHub peronal access token (with restrictions: can read user info only) +# https://docs.github.com/en/rest/guides/getting-started-with-the-rest-api#using-personal-access-tokens= +personal_access_token= +username= + +# Get stars count +stars=$(curl -sSI -u ${username}:${token} https://api.github.com/users/${username}/starred?per_page=1 | + grep -i '^link:' | egrep -o 'page=[0-9]+' | tail -1 | cut -c6-) +# Get pages count +pages=$((stars/100+1)) + +{ + for page in `seq ${pages}`; do + curl -sS -u ${username}:${token} \ + "https://api.github.com/users/${username}/starred?per_page=100&page=${page}" | + jq -r '.[].clone_url' + done +} diff --git a/src/http b/src/http new file mode 100755 index 0000000..296894b --- /dev/null +++ b/src/http @@ -0,0 +1,110 @@ +#!/usr/bin/env bash + +http_version=0.5.1 # this script version. + +set -o monitor # for job control. +set -o errexit # exit if error occurs. + +# Defaults +HOST=0.0.0.0 +PORT=8000 +DIR=$PWD + +# Check Python 3 +if ! hash python3 2>/dev/null; then + echo -e "$0: Python 3 executable not found." >&2 + exit 1 +fi + +http_print_help() { + cat <<-EOF +Run Python 3 builtin HTTP Server. + +Usage: http [-v|--version] [-h|--help] [-c|--cgi] [-f|--firefox] + [[:]] [] [] + +Options: + -c, --cgi run as CGI Server. + -f, --firefox open URL in Firefox. + -h, --help print this help message and exit. + -v, --version print version and exit. + +Arguments: + host to bind. Default: 0.0.0.0 + port to bind. Default: 8000 + directory to serve. Default: current directory. +EOF + exit 0 +} + +while (( "$#" )); do + case "$1" in + -c|--cgi) + CGI='--cgi';; + -f|--firefox) + FOX=1;; + -h|--help) + http_print_help;; + -v|--version) + echo "http v$http_version"; exit 0;; + -*|--*) + echo -e "$0: Bad option: $1" >&2 + exit 1 + ;; + *) + if [ -d "$1" ] + then + # If direcory exists set it as root DIR. + DIR="$1" + elif [[ "$1" =~ ^[0-9]+$ ]] + then + # If value is integer set it as PORT. + PORT="$1" + elif [[ "$1" =~ \ + ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] + then + # Value like IPv4 address. Not necessary. + HOST="$1" + elif [[ "$1" == 'localhost' ]] + then + # Nuff said. + HOST="$1" + elif [ "$(grep -o : <<<"$1")" ] + then + # If value like 'localhost:4000' + HOST="$(echo "$1" | cut -d ":" -f 1)" + PORT="$(echo "$1" | cut -d ":" -f 2)" + else + echo -e "$0: Bad option: $1$" >&2 + exit 1 + fi + ;; + esac + shift +done + +# Check available port and retry 3 times. +retries=1 +while [ "$(ss -tanp | grep -o "$PORT")" ]; do + if [[ "$retries" == 4 ]] + then + echo -e "Max number of retries reached! Exiting." >&2 + exit 1 + else + : + fi + + # Increase port if is already in use. + PORT_USED="$PORT" + let PORT++ + echo -e "Port $PORT_USED is already in use! + Switching to: $PORT" | sed 's/^ *//g' >&2 + + let retries++ +done + +# Run Python http.server. +echo -e "Serve: $DIR\tPress ^C to stop serving." +python3 -m http.server $CGI --bind $HOST $PORT --directory $DIR & +[[ "$FOX" == 1 ]] && firefox http://$HOST:$PORT/ +fg diff --git a/src/log b/src/log new file mode 100755 index 0000000..cce55bc --- /dev/null +++ b/src/log @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +# Simple log viewing tool. + +log_version=0.1 + +nginx_logs=/var/log/nginx +exim4_logs=/var/log/exim4 + +print_help() { + cat <<- EOF +Print logs to STDOUT. + +Usage: log [-vhtf] + +Options: + -t [], - use tail with 'num' lines. + -f, --tailf use 'tail -f'. + -h, --help print this message and exit. + -v, --version print version and exit. + +Available logs (): + na nginx access logs ($nginx_logs) + ne nginx error logs ($nginx_logs) + ex exim4 logs ($exim4_logs) +EOF + exit 0 +} + +print_log() { + # Print logs to STDPUT with cat or zcat. + + logfiles="$(sort -Vr <<< "$1")" + + for logfile in $logfiles; do + echo "* File: $logfile" + if [[ "$(file --mime-type -b $logfile)" == application/gzip ]] + then catzcat=zcat + else catzcat=cat + fi + + "$catzcat" "$logfile" + done +} + +tailf() { + logfile="$(egrep -v '\.(1|gz)' <<< "$1")" + tail -f "$logfile" +} + +parse_num() { + arg="${1:1}" + opt="$2" + if [[ "$arg" =~ ^[0-9]+$ ]]; then + num="$arg"; sft=1 + elif [[ "$arg" =~ ^t[0-9]+$ ]]; then + num="${arg:1}"; sft=1 + elif [[ "$arg" == 't' ]] && [ "$opt" ]; then + num="$opt"; sft=2 + else + echo "$0: argument must be an integer" >&2 + exit 1 + fi +} + +# Args pre-parser +[[ "$@" ]] || print_help +if [[ ! "$@" =~ na|ne|ex ]] && \ + [[ ! "$@" =~ -\h|--help|--version ]]; then + echo -n "$0: no log name provided." >&2 + echo " See 'log --help' for info." >&2 + exit 1 +fi + +while (( "$#" )) +do + case "$1" in + na) logfiles="$(find "$nginx_logs" -type f -name "access*")"; + shift;; + ne) logfiles="$(find "$nginx_logs" -type f -name "error*")"; + shift;; + ex) logfiles="$(find "$exim4_logs" -type f)" + shift;; + -h|--help) print_help;; + -v|--version) echo "log $log_version"; exit 0;; + -f|--tailf) [ "$logfiles" ] && tailf "$logfiles";; + -t|-t*|-[0-9]*) parse_num "$1" "$2"; shift "$sft";; + *) echo "$0: bad argument: $1" >&2; exit 1;; + esac +done + +if [ "$num" ]; then + print_log "$logfiles" | tail -n "$num" +else + print_log "$logfiles" +fi diff --git a/src/md2 b/src/md2 new file mode 100755 index 0000000..a95fc83 --- /dev/null +++ b/src/md2 @@ -0,0 +1,157 @@ +#!/usr/bin/env bash + +_md2_help() { + cat <<- EOF +Convert Markdown to different formats. + +Usage: md2 [-v|-h] [args].. + md2 html [] + md2 man [] + md2 pdf [] + +Options: + -h, --help print this message and exit. + -v, --version print version and exit. + +Commands: + html convert to HTML. + man|roff convert to ROFF. Pass only for preview. + pdf convert to PDF. + +Also you can use se symlbolic links to md2 to run commands. +E.g.: symlink md2pdf -> md2 runs \`md2 pdf\` + +md2 is just Pandoc wrapper. Edit md2 source code for customize. +EOF + exit 0 +} + +_md2_usage() { + echo "Usage: ${func:1} []" + [ "$func" == "_md2man" ] && \ + echo "Pass only for preview." + exit 0 +} + +_md2_version() { + cat <<- EOF + md2 0.1.0 + EOF + exit 0 +} + +_md2() { + # Wrapper function + local inp out args preview + + [[ ! "$@" ]] && _md2_usage + + while (( "$#" )); do + case "$1" in + -h|--help) _md2_usage ;; + -v|--version) _md2_version ;; + -*) echo "${func:1}: $1: bad option." >&2 + _md2_usage ;; + *) args+=("$1"); shift ;; + esac + done + + if [ ${#args[@]} -eq 2 ]; then + inp="${args[0]}"; out="${args[1]}" + elif [ ${#args[@]} -eq 1 ]; then + inp="${args[0]}"; out="${args[0]%%.*}.${ext}" + [ "$func" == "_md2man" ] && preview=1 + else + _md2_usage + fi + + echo -e "\e[1mInput:\e[0m \e[37m${inp}\e[0m" + + if [ "$preview" ]; then + _md2man_preview "$inp" + else + echo -e "\e[1mOutput:\e[0m \e[37m${out}\e[0m" + eval "$func" "$inp" "$out" + fi +} + +_md2_run() { + # Another wrapper function. + case "$1" in + -h|--help) _md2_help ;; + -v|--version) _md2_version;; + html) func=_md2html; ext='html'; shift;; + man|roff) func=_md2man; ext='1'; shift;; + pdf) func=_md2pdf; ext='pdf'; shift;; + *) _md2_help ;; + esac + + _md2 "$@" + exit $? +} + +# BEGIN Pandoc wrapper functions ########################################## + +_md2html() { + # HTML + pandoc \ + --from=markdown \ + --to=html \ + --standalone \ + "$1" \ + --output="$2" \ + --variable document-css=true \ + --variable linkcolor='[HTML]{0000ff}' \ + --metadata title="${1%.*}" +} + +_md2man() { + # ROFF + pandoc \ + --from=markdown \ + --to=man \ + --standalone \ + "$1" \ + --output="$2" +} + +_md2man_preview() { + # ROFF + # View ROFF in pager without saving. + pandoc --standalone --to man ${1:-"-"} | # Read into Pandoc + groff -T utf8 -man | # format for pager. + sed 1,4d | head -n -4 | # Chop off 4 leading/trailing + # (empty) lines. + less +} + +_md2pdf() { + # PDF + pandoc \ + --from=markdown-implicit_figures \ + --to=pdf \ + "$1" \ + --output="$2" \ + --pdf-engine=xelatex \ + --variable mainfont='FreeSans' \ + --variable fontsize='12pt' \ + --variable urlcolor='[HTML]{0000ff}' \ + --variable pagestyle=empty \ + --variable margin-left='20mm' \ + --variable margin-right='20mm' \ + --variable margin-top='20mm' \ + --variable margin-bottom='20mm' \ + --highlight-style haddock +} + +# END Pandoc wrapper functions ############################################ + +case "$0" in + *md2) _md2_run "$@"; shift "$#" ;; + *md2html) func=_md2html; ext='html' ;; + *md2man) func=_md2man; ext='1' ;; + *md2pdf) func=_md2pdf; ext='pdf' ;; + *) echo "$0: bad command"; _md2_help;; +esac + +_md2 "$@" diff --git a/src/mega b/src/mega new file mode 100755 index 0000000..ab438c5 --- /dev/null +++ b/src/mega @@ -0,0 +1,121 @@ +#!/usr/bin/env sh + +# ___ ___ __ __ __ +# /' __` __`\ /'__`\ /'_ `\ /'__`\ +# /\ \/\ \/\ \/\ __//\ \L\ \/\ \L\.\_ +# \ \_\ \_\ \_\ \____\ \____ \ \__/.\_\ +# \/_/\/_/\/_/\/____/\/___L\ \/__/\/_/ +# /\____/ +# \_/__/ +# +# * MEGAcmd wrapper by ge +# See more info about megacmd here: https://github.com/meganz/MEGAcmd +# +# This software is not affiliated with original MEGAcmd developers! +# +# * License: The Unlicense +# +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# For more information, please refer to + +_mega_help_wrapper() { + cat << 'EOF' + + MEGAcmd wrapper script + ___ ___ __ __ __ + /' __` __`\ /'__`\ /'_ `\ /'__`\ + /\ \/\ \/\ \/\ __//\ \L\ \/\ \L\.\_ + \ \_\ \_\ \_\ \____\ \____ \ \__/.\_\ + \/_/\/_/\/_/\/____/\/___L\ \/__/\/_/ + /\____/ + \_/__/ + +Usage: mega command [] [] + +mega is a megacmd wrapper that allows you to run megacmd commands as 'mega command' +instead of 'mega-command'. + +Wrapper commands: + pid print mega-cmd-server pid + ps show mega-cmd-server process + tail 'tail -f $HOME/.megaCmd/megacmdserver.log' + kill send SIGINT to mega-cmd-server process + lsof lsof mega-cmd-server, pass output to PAGER if set + [default: /usr/bin/less if exist or /usr/bin/more] +Wrapper options: + -h, -?, --help print this message, 'mega-help' output and exit. + -u, --usage print only wrapper help message amd exit. +Tips: + mega cmd enter to MEGA shell + mega quit stop MEGA server (mega-cmd-server) + +@@@ This software is not affiliated with original MEGAcmd developers! @@@ +MEGAcmd User Guide: https://github.com/meganz/MEGAcmd/blob/master/UserGuide.md +EOF + if [ "$1" ]; then + echo -e '\nSee available commands below (there is mega-help original output)' + echo -e '\n---\n' + mega-help + else + echo "Run 'mega help' or 'mega-help' to see available MEGAcmd commands." + fi + exit 0 +} + +# Select pager +if [ -z "$PAGER" ]; then + if [ -f /usr/bin/less ]; then + PAGER=/usr/bin/less + elif [ -f /usr/bin/more ]; then + PAGER=/usr/bin/more + else + echo 'Pager not found! Use PAGER environment variable to set pager' >&2 + fi +fi + +[[ "$@" ]] || { _mega_help_wrapper 'with mega-help'; } +# Add some additional commands +case "$1" in + -h|-\?|--help) _mega_help_wrapper 'with mega-help';; + -u|--usage) _mega_help_wrapper;; + pid) pgrep mega-cmd-server; exit "$?";; # check MEGA server + ps) ps u -C mega-cmd-server; exit "$?";; # show MEGA server process + tail) tail -f $HOME/.megaCmd/megacmdserver.log; exit "$?";; + kill) killall -2 `pgrep mega-cmd-server`; exit "$?";; + lsof) if [ "$PAGER" ]; then + lsof -p `pgrep mega-cmd-server` | "$PAGER" + else + lsof -p `pgrep mega-cmd-server` + fi + exit "$?";; + *) : # do nothing for another cases +esac + +_command="$1" # fetch command to execute +shift # shift arguments to prevent running wrong commands like + # 'mega-help help' + +# Run command with arguments +"mega-${_command}" "$@" diff --git a/src/parts b/src/parts new file mode 100755 index 0000000..4b7b2f4 --- /dev/null +++ b/src/parts @@ -0,0 +1,197 @@ +#!/usr/bin/env bash + +# +# * Parts +# +# Store and view your code snippets in files. +# + +partsversion=0.2 +[ "$PARTSPATH" ] || PARTSPATH=${HOME}/.local/share/parts +[ -d "$PARTSPATH" ] || mkdir -p "$PARTSPATH" || + { echo "Error: Cannot create PARTSPATH: $PARTSPATH"; exit 1; } +parts=(`find "$PARTSPATH" -type f`) + +pt_help() { + cat <<- EOF +Store and view code snippets (parts). + +Usage: parts [-v| --version] [-h | --help] [-i | --interactive] + [-a | --add ] [-l | --lang ] + [-t | --tag ] [-d | --descr ] + [-r | --regex ] + +Options: + -i, --interactive add a part in interactive mode. + -a, --add part text. + -l, --lang part's language. + -t, --tag part's tag. + -d, --descr part description + -r, --regex search part by regex (grep -P). + -h, --help print this help message and exit. + -v, --version print version and exit. + +Environment variables: + PARTSPATH + path to save parts. Default: \$HOME/.local/share/parts + PARTSHL + code syntax highlighting. Depends 'highlight' package. Set + the value of this variable to to enable, e.g. "1" + EDITOR + editor to be used in interactive mode. +EOF + exit 0 +} + +pt_random() { + local dict=({a..z} {A..Z} {0..9} - _) + for i in {1..32}; do + echo -n ${dict[$RANDOM%64]} + done +} + +pt_interactive() { + local _temp=`mktemp` + local _edit="$EDITOR" + local _partfile="${PARTSPATH}/`pt_random`.txt" + cat > "$_temp" <<- EOF +# Type your data between BEGIN and END comments. +# DO NOT REMOVE COMMENTS! +# See highlight(1) '--syntax' for Lang options. + +# Part BEGIN + +# Part END + +# Lang BEGIN + +# Lang END + +# Tag BEGIN + +# Tag END + +# Descr BEGIN + +# Descr END +EOF + "$_edit" "$_temp" + + # Check file + pt_parse_part "$_temp" + [ "$partcode" ] || { echo Exited.; rm "$_temp"; exit 130; } + + if cat "$_temp" > "$_partfile"; then + echo -e "\e[1mSaved to:\e[0m $_partfile" + rm "$_temp" + else + echo "Cannot write part. Saved to temporary file $_temp" + exit 1 + fi +} + +pt_add() { + local _partfile="${PARTSPATH}/`pt_random`.txt" + cat > "$_partfile" <<- EOF +# Type your data between BEGIN and END comments. +# DO NOT REMOVE COMMENTS! +# See highlight(1) '--syntax' for Lang options. + +# Part BEGIN +$partcode +# Part END + +# Lang BEGIN +$partlang +# Lang END + +# Tag BEGIN +$parttag +# Tag END + +# Descr BEGIN +$partdescr +# Descr END +EOF + [ -f "$_partfile" ] && { echo -e "\e[1mSaved to:\e[0m $_partfile" + } || { echo "Cannot write part. Saved to temporary file $_temp"; exit 1; } +} + +pt_parse_part() { + partcode="$(grep -zoP \ + '(?<=# Part BEGIN\n)(((.*)\n)*)(?=# Part END)' "$1" | tr -d '\0')" + partlang="$(grep -zoP \ + '(?<=# Lang BEGIN\n)(((.*)\n)*)(?=# Lang END)' "$1" | tr -d '\0')" + parttag="$(grep -zoP \ + '(?<=# Tag BEGIN\n)(((.*)\n)*)(?=# Tag END)' "$1" | tr -d '\0')" + partdescr="$(grep -zoP \ + '(?<=# Descr BEGIN\n)(((.*)\n)*)(?=# Descr END)' "$1" | tr -d '\0')" +} + +pt_view_part() { + pt_parse_part "$1" + echo -e "\e[94m$1" + [ "$partdescr" ] && echo -e "\e[94;1m+\e[0m \e[1m$partdescr\e[0m" + [ "$parttag" ] && echo -e "\e[94;1m#\e[0m \e[1m$parttag\e[0m" + echo -e '\e[0m' + + if [ "$PARTSHL" ]; then + [ "$partlang" ] || partlang=txt + echo "$partcode" | highlight -O ansi -S "${partlang,,}" + else + echo "$partcode" + fi + echo +} + +pt_search_regex() { + local _list="$(grep -Prl "$1" "$PARTSPATH")" + [ "$_list" ] && { + while read -r part; do + pt_view_part "$part" + done <<< "$_list" + } || { echo -e "\e[91mNo matches for $1\e[0m"; exit 1; } +} + +# +# * Args parser +# + +# Transform long options to short ones +for arg in "$@"; do + shift + case "$arg" in + --version) set -- "$@" "-v" ;; + --help) set -- "$@" "-h" ;; + --interactive) set -- "$@" "-i" ;; + --add) set -- "$@" "-a" ;; + --lang) set -- "$@" "-l" ;; + --tag) set -- "$@" "-t" ;; + --descr) set -- "$@" "-d" ;; + --regex) set -- "$@" "-r" ;; + *) set -- "$@" "$arg";; + esac +done + +while getopts "vhia:l:t:d:r:" opt; do + case "$opt" in + v) echo parts $partsversion; exit 0;; + h) pt_help;; + i) pt_interactive;; + a) partcode="$OPTARG";; + l) partlang="$OPTARG";; + t) parttag="$OPTARG";; + d) partdescr="$OPTARG";; + r) pt_search_regex "$OPTARG";; + esac +done + +[[ "$@" ]] || { + for _part in "${parts[@]}"; do + pt_view_part "$_part" + done +} + +[[ "$@" =~ -a|--add ]] && { + pt_add +} diff --git a/src/rand b/src/rand new file mode 100755 index 0000000..966821a --- /dev/null +++ b/src/rand @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +_count=1 + +rand_help() { + cat <<- EOF +Print random array item. + +Usage: rand [-vhc] ... + +Options: + -c print random items [default: $_count] + -h print this help message and exit. + -v print version and exit. + +Examples: + $ rand duck chicken swan + duck + $ echo bee butterfly fly | xargs rand + bee + $ rand -c 36 {a..z} {A..Z} | xargs echo | sed 's% %%g' + KAjcgyoANcEvNWDhkxuHlxJjFWDmVqglVGAZ +EOF + exit 0 +} + +[[ "$@" ]] || rand_help + +while getopts vhc: OPT; do + case "$OPT" in + v) echo rand 0.1; exit 0;; + h) rand_help;; + c) _count="$OPTARG";; + esac +done + +shift "$((OPTIND-1))" + +if [[ "$@" ]]; then + _args=("$@") +else + exit 1 +fi + +for (( i=0; i<"$_count"; i++ )); do + echo ${_args[$(($RANDOM%${#_args[@]}))]} +done diff --git a/src/safeeject b/src/safeeject new file mode 100755 index 0000000..d6fe206 --- /dev/null +++ b/src/safeeject @@ -0,0 +1,113 @@ +#!/usr/bin/env bash + +set -o errexit + +print_help() { + cat <<- EOF +Safely remove an external drive. Unmount and power-off device via udisksctl. + +Usage: safeeject [-hv] [] + +Options: + -h print this help message and exit. + -v print version and exit. +EOF +} + +yesno() { + local answer= + [ "$ASSUME_YES" ] && return 0 + + while [ ! "$answer" ]; do + echo -en "$@ (answer 'yes' to proceed) " + read -r reply + case "${reply,,}" in + yes) answer=0;; + *) answer=1;; + esac + done + return "$answer" +} + +resolve_device() { + case "$1" in + /dev/sd*|/dev/hd*) + local del="$(echo "$1" | grep -Po '(?<=[a-z])([0-9]+)$')";; + /dev/nvme*) + local del="$(echo "$1" | grep -Po '(?<=[0-9])?([a-z])([0-9]+)$')";; + *) + echo -e "\e[91mCannot parse device name\e[0m"; return;; + esac + echo "${1//$del}" +} + +while getopts hv OPT; do + case $OPT in + h) print_help; exit 0;; + v) echo 1.1; exit 0;; + esac +done +shift $((OPTIND-1)) +point="$1" + +if [[ "$UID" != 0 ]]; then + echo -e "\e[91mYou arn't root!\e[0m" >&2; exit 1 +fi + +if ! hash udisksctl &>/dev/null; then + echo -e '\e[91mudisksctl utility not found!\e[0m' >&2; exit 1 +fi + +[ "$point" ] || { echo -e '\e[91mNo mountpoint specified\e[0m'; exit 1; } + +if [[ "$point" == '/' ]]; then + echo -e '\e[91mCannot unmount root\e[0m'; exit 1 +fi + +echo -e "Mountpoint: \e[1m${point%%/}\e[0m" + +partition="$(mount | grep " ${point%%/} " | tee /dev/stderr | awk '{print $1}')" +if [ "$partition" ]; then + echo -e "Partition: \e[1m${partition}\e[0m" +else + echo -e "\e[91mNo filesystems mounted on ${point%%/}\e[0m"; exit 1 +fi + +device="$(resolve_device "$partition")" +echo -e "Device: \e[1m${device}\e[0m" +if [[ ! "$device" =~ Cannot.* ]]; then + fdisk --color=never --list "$device" +else + exit 1 +fi + +# Do not allow poweroff the system disk! +root_partition="$(mount | grep " / " | tee /dev/stderr | awk '{print $1}')" +root_device="$(resolve_device "$root_partition")" +if [[ "$device" == "$root_device" ]]; then + echo -e '\e[91mYou cannot eject primary disk!\e[0m'; exit 1 +fi + +echo -e "\n\e[91mDo not continue if device is not detected correctly," \ + "instead unmount and poweroff the device manually!\e[0m" + +if yesno "Is $device the correct device?"; then + echo Sync to disk ... + sync + sleep 5 + echo Unmounting $partition ... + if udisksctl unmount -b "$partition"; then + sleep 1 + else + exit 1 + fi + echo Power off device $device ... + if udisksctl power-off -b "$device"; then + sleep 2 + echo -e '\e[92mNow you can safely eject device\e[0m' + else + exit 1 + fi +else + echo Abort; exit 1 +fi diff --git a/src/unflac b/src/unflac new file mode 100755 index 0000000..7e1dd69 --- /dev/null +++ b/src/unflac @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +unflac_usage() { + echo 'Convert FLAC to MP3 320kbps with metadata via ffmpeg.' + echo 'Usage: unflac [-h|--help] [-d|--delete] []' + echo 'Options:' + echo ' -h, --help print this message and exit.' + echo ' -d, --delete delete original FLAC file after conversion.' + echo 'For multiple files: for file in *; do unflac -d "$file"; done' +} + +[ "$1" ] || { unflac_usage; exit 0; } + +while (( "$#" )); do + case "$1" in + -h|--help) unflac_usage; exit 0;; + -d|--delete) delete_flac=1; shift;; + -*) echo Unknown option $1 >&2; exit 1;; + *) flac="$1"; shift;; + esac +done + +if [ -f "${flac//\.flac/\.mp3}" ]; then + echo File \*.mp3 already exists! +else + echo -e "\e[30;102mFILE: $flac\e[0m"; + if ffmpeg -i "$flac" -ab 320k -map_metadata 0 -id3v2_version 3 "${flac//\.flac/}.mp3"; then + echo OK + [ "$delete_flac" ] && { echo -"Deleting original FLAC file: $flac"; rm -f "$flac"; } + fi +fi diff --git a/src/upload b/src/upload new file mode 100755 index 0000000..65797ff --- /dev/null +++ b/src/upload @@ -0,0 +1,323 @@ +#!/usr/bin/env bash + +# upload +# +# Upload files to remote server. upl implements handy interface for +# scp (OpenSSH secure file copy). upl cannot be called a full-fledged +# frontend (or wrapper) for scp, since it does not allow changing the scp +# parameters through its interface without editing the source code. +# +# Instead, upl implements the concept for accessing frequently used remote +# servers. All you need to download is a short server name and filenames. +# See more info in 'upl --help' (upl_help() function below). + +upl_version=0.1.1 +upl_config=$HOME/.uploadrc + +# Default values. +# +# Default destination dir. Can be set in $upl_config file +# in server URI or passed as CLI argument. +# +destdir='~' +# Default SCP commands. +# +# This is just templates for printf. +# +# If you have SSH keys: +scp_cmd='scp -r %s %s' +# If you use password: +scp_cmd_pass='sshpass -p %s scp -r %s %s' + +# HELP +######## + +upl_help() { + cat <<- EOF +Upload files to remote server. + +Usage: upload [-v|--version] [-h|--help] [-a|--aliases] [-p|--prompt] + [-d|--dest=] [] + [] ... + +Options: + -d, --dest= absolute path to destination dir on remote server. + -a, --aliases print available server aliases and exit. + -p, --prompt show uploading confirmation prompt. + -v, --version print version and exit. + -h, --help print this help message and exit. + +How to: +1. Create a config file named '.uploadrc' in your home directory. It should +list servers with their aliases in the following format: + [] +Example: + srv1 root@srv.example.com password + srv2 admin@123.45.67.89:/uploads +To upload testfile.txt to srv2 just run: + upload srv2 testfile.txt + +If you specify only one server in the file, then it will be used by default +and you will not need to specify its alias when uploading files. + +The URI address and destination directory specified in the command line +override the data from the .uploadrc. +EOF + exit 0 +} + +# CONFIG PARSER +################# + +upl_check_config() { + # Return error if $upl_config not exists or is empty. + if [ ! -f "$upl_config" ]; then + echo "$0: $upl_config: file not exist" >&2; exit 1 + elif [ ! -s "$upl_config" ]; then + echo "$0: no servers set in $upl_config" >&2; exit 1 + fi +} + +upl_load_config() { + # Load configuration from file. + # + # Check syntax and return $servers + + # Remove comments from config. + servers="$(sed '/^#/d;/^$/d;s/#.*//g' < "$upl_config")" + + # Check syntax. + local ln=0 # line number + while read -r line + do + let ln++ + # Invalid URI. + if [[ ! "$line" =~ \s*.+@.+ ]]; then + echo "$0: $upl_config: URI is not set in line $ln" >&2 + exit 1 + fi + # Parameters count. + local words=$(wc -w <<< "$line") + if [ $words -gt 3 ]; then + echo "$0: $upl_config: too many params in line $ln" >&2 + exit 1 + elif [ $words -eq 1 ]; then + echo "$0: $upl_config: invalid server entry in line $ln" >&2 + exit 1 + fi + done <<< "$servers" +} + +upl_get_aliases() { + # Collect server aliases. + while read -r line + do + aliases+=("${line%% *}") + done <<< "$servers" +} + +upl_set_destdir() { + # Set up $destdir. + # + # Default value is '~' + # + # Arguments: + # $1 -- URI string. + if [[ "$1" =~ .+:.+ ]]; then + local uri="${1##*:}" # '/path' from 'user@server:/path' + fi + + # Keep default value if destination dir is not set anywhere. + if [[ ! "$destdir" = '~' ]] + then + : # That means '$destdir is set as CLI argument.' + # Nothing to do. This has max priority. + else + if [ "$uri" ]; then destdir="$uri"; fi + fi +} + +# FUNCTIONS +############# + +upl_show_list() { + # Print table lines with server entries. + # + # Passwords will not shown. + + local table_content="$(echo "$servers" | + awk 'BEGIN {print "| " "Alias" " | " "URI" " |"} + {print "| " $1 " | " $2 " |"}' | + column -t + )" + + local table_border=$(\ + sed 's%|%+%g;s%[^+]%-%g;' <<< "$(head -n 1 <<< "$table_content")") + + echo "$table_border" + echo "$(head -n 1 <<< "$table_content")" + echo "$table_border" + echo "$(sed '1d' <<< "$table_content")" + echo "$table_border" + exit 0 +} + +upl_yesno() { + # "Yes|No" interactive dialog. + # + # Return exit code 0 or 1. + local question="$1" # message + local yes=0 + local no=1 + local answer=2 + + while [ $answer -eq 2 ] + do + echo -en "$question [y/n] " + read -r reply + case "$reply" in + y|Y|Yes|YES) answer=$yes;; + n|N|No|NO) answer=$no;; + *) echo 'Please, answer y or n';; + esac + done + + return "$answer" +} + +# ARGS PARSER +############### + +upl_getopts() { + # GNU-style CLI options parser. + # + # Parse --opt VAL and --opt=VAL options. + # Requires 2 arguments: $1, $2. + # Return: + # $opt - option name. + # $arg - option's value. + # $sft - value for shift. + + if [[ "$1" =~ .+=.+ ]]; then + opt="${1%%=*}"; arg="${1#*=}"; sft=1 + elif [[ ! "$1" =~ .+=$ ]] && \ + [ "$2" ] && [ "${2:0:1}" != '-' ] + then + opt="$1"; arg="$2"; sft=2 + else + opt="$1" + if [[ "$1" =~ .+=$ ]]; then opt="${1:0: -1}"; fi + echo "missing argument for: $opt" >&2; exit 1 + fi +} + +upl_is_uri() { + # Check URI pattern like user@server from string $1 + # + # Return $is_uri + is_uri=1 + + if [[ "$1" =~ .+@.+ ]]; then is_uri=0; fi + return $is_uri +} + +# Load configuration +upl_check_config +upl_load_config # get $servers +upl_get_aliases # get $aliases from $servers + +[[ "$@" ]] || upl_help +while (( "$#" )) +do + case "$1" in + -p|--prompt) prompt=1; shift;; + -a|--aliases) upl_show_list; shift;; + -d|--dest|--dest=*) + sft=1 # Set default shift value. + upl_getopts "$1" "$2" + destdir="$arg" + shift "$sft";; + -h|--help) upl_help;; + -v|--version) echo "upl v$upl_version"; exit 0;; + -*) echo "$0: bad option: $1" >&2; exit 1;; + *) + sft=1 # Set default shift value. + + if [[ "${aliases[@]}" =~ "$1" ]] # is server alias? + then + server_alias="$1" + elif upl_is_uri "$1" # is user@server URI? + then + server_uri="$1"; + if [ "$2" ] && [ "${2:0:1}" != '-' ]; then + ARGS+=("$2"); sft=2 + fi + else + ARGS+=("$1"); # collect positional arguments. + fi + shift "$sft";; + esac +done + +# Exit if no servers found in $upl_conf file. +if [ -z "$servers" ]; then + echo "$0: $upl_config: no server entries found" >&2; exit 1 +fi + +# Exit if server alias is not set. +if [ "${#aliases[@]}" -gt 1 ] && \ + [ ! "$server_alias" ] && \ + [ ! "$server_uri" ] +then + echo "Please, specify server alias as argument or pass URI." >&2 + echo "Run 'upload -a' to see available aliases." \ + "See 'upload --help' for more." >&2 + exit 1 +elif [ "${#aliases[@]}" -eq 1 ] && \ + [ ! "$server_uri" ] +then + # Use single alias as default. + server_alias=${servers%%* } +fi + +# Get basic data: URI and destination dir. +server_entry=($(grep "$server_alias" <<< "$servers")) +[ "$server_uri" ] || server_uri=${server_entry[1]} +upl_set_destdir "$server_uri" + +if [ "$prompt" ]; then + echo "Uploading to..." + echo + echo "Remote server: $server_uri" + echo "Destination dir: $destdir" + echo "Files to upload:" + echo -en ' ' + echo -e "${ARGS[@]}" | fmt -t + echo + if upl_yesno "Do you want to continue?" + then echo # just print new line + else echo Abort; exit 1 + fi +fi + +# Required data. +# +# There is: +# | user@server:/path | password | file list +reqs="${server_uri%%:*}:$destdir ${server_entry[2]} ${ARGS[@]}" + +# Get upload command. +if [ "${server_entry[2]}" ]; then + upload_cmd="$(echo "$reqs" | + awk '{print "sshpass -p " $2 " scp -r "} + {for(i=3;i<=NF;i++){printf "%s ", $i}} + {print " " $1}' | tr -d '\n')" +else + upload_cmd="$(echo "$reqs" | + awk '{print "scp -r "} + {for(i=2;i<=NF;i++){printf "%s ", $i}} + {print " " $1}' | tr -d '\n')" +fi + +# Upload files! +eval "$upload_cmd" diff --git a/src/view b/src/view new file mode 100755 index 0000000..53ad03c --- /dev/null +++ b/src/view @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +view_version=0.8 + +view_help() { + cat <<- EOF +Print highlighted text to STDOUT. + +Usage: view [-vhpn] [] + +Options: + -l, -p, --pager pass output to pager (less). + -n, --lines show line numbers. + -h, --help print this help message and exit. + -v, --version print version and exit. +EOF + exit 0 +} + +view_langdetect() { + if [[ "$1" =~ .+\..+ ]]; then + echo "${1##*.}" + else + # Lookup for shebang + echo "$_file" | head -n 1 | cut -d ' ' -f 2 | + sed 's%\#\!%%g' | xargs basename + fi +} + +view_hl() { + _file="$(cat "${1:-/dev/stdin}")" + + if ! local lang=$(view_langdetect "$1"); then + langsh=sh + fi + + case "$lang" in + python|python2|python3|python3.*) lang=py;; + esac + + echo "$_file" | highlight -O ansi -S "$lang" +} + +if ! hash highlight 2>/dev/null; then + echo "highlight executable not found" >&2; exit 1 +fi + +# Do nothing if no arguments passed and no STDIN. +[[ -t 0 && -z "$1" ]] && view_help + +while (( "$#" )); do + case "$1" in + -v|--version) echo view $view_version; exit 0;; + -h|--help) view_help;; + -l|-p|--pager) _pager=1; shift;; + -n|--lines) _lines=1; shift;; + -pn|-np) _pager=1; _lines=1; shift;; + -*) echo "Invalid option: $1" >&2; exit 1;; + *) args+=("$1"); shift;; + esac +done + +if [ "$_pager" ]; then + _pager=" | less -R" + [ "$_lines" ] && _pager_opts=" -N" +else + [ "$_lines" ] && _catn=" | cat -n" +fi + +eval view_hl "$args" "$_pager" "$_pager_opts" "$_catn" diff --git a/src/yad b/src/yad new file mode 100755 index 0000000..9b2dc65 --- /dev/null +++ b/src/yad @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +yad_version=0.1.2 +API_URL='https://cloud-api.yandex.net/v1/disk/public/resources/download?public_key=' + +yad_help() { + cat <<- EOF +Dowload file from Yandex.Disk cloud storage. + +Usage: yad [-Vhdv] + +Options: + -d get direct link. + -v verbose output. + -V, --version print version and exit. + -h, --help print this help message and exit. +EOF + exit 0 +} + +[[ "$@" ]] || yad_help + +while (( "$#" )); do + case "$1" in + -V|--version) echo "$yad_version"; exit 0;; + -h|--help) yad_help;; + -*) + for i in $(seq 2 ${#1}); do opts+=("-${1:i-1:1}"); done + + for opt in "${opts[@]}"; do + case "$opt" in + -d) direct_only=1;; + -v) verbose=1;; + *) echo "$0: Bad option: $opt" >&2; exit 1;; + esac + done + shift;; + *) + if [[ ! "$1" =~ https://(disk.yandex.ru|yadi.sk)/.+/.+ ]] + then + echo "$0: Bad link: $1" >&2; exit 1 + fi + share_link+=("$1") + if [ ${#share_link[@]} -ne 1 ]; then + echo "$0: Too many arguments" >&2; exit 1 + fi + shift;; + esac +done + +if [ ${#share_link[@]} -eq 0 ]; then + echo "$0: No share link specified." >&2; exit 1 +fi + +# Check the share link availability. +response=$(curl -sSw %{http_code} -o /dev/null "$share_link") +[ "$verbose" ] && echo "Responce code: $response" +if [[ ! "$response" =~ 200|302 ]]; then + echo "$0: Bad link: $share_link" >&2; exit 1 +fi + +[ "$verbose" ] && { + echo "Share link: $share_link" + echo "API Request URL: ${API_URL}${share_link}" +} + +# Get direct URL from Yandex Public API. +direct_link="$(python3 -c \ +"import json +dl = json.loads('$(curl -sS "${API_URL}${share_link}")') +print(dl['href'])" +)" + +[ "$verbose" ] && echo "Direct link: $direct_link" + +if [ "$direct_only" ]; then + echo "$direct_link" + exit 0 +fi + +# Download file. +file_name="$(grep -oP '(?<=filename\=).+(?=&disposition)' <<< "$direct_link")" + +[ "$verbose" ] && { + echo "File name: $file_name" + curl_opts='-v' +} + +curl -L "$curl_opts" "$direct_link" -o "$file_name"