This commit is contained in:
ge 2022-07-27 08:31:34 +03:00
commit bb07073c6a
21 changed files with 2351 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
untracked/

25
README Normal file
View File

@ -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`.

24
UNLICENSE Normal file
View File

@ -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 <http://unlicense.org/>

245
src/appimage Executable file
View File

@ -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 <http://unlicense.org/>
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] [<arguments>]
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

187
src/btw Executable file
View File

@ -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 <http://unlicense.org/>
#
# ###
#
# btw depends these packages:
# `acpi` ; <https://sourceforge.net/projects/acpiclient/>
# `libnotify` ; <https://salsa.debian.org/gnome-team/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 <val>]
[-p|--preriod <seconds>] [-l|--log <file>]
[-s|--summary <summary>] [-b|--body <body>] [-w|--watch]
Options:
-v, --version print version and exit.
-h, --help print this help message and exit.
-c, --crit <val> battery critical value (in percents), default: 10
-p, --preriod <seconds> battery check out period in seconds, default: 30
-l, --log <file> log file, default: ~/.cache/btwatchdog.log
-s, --summary <summary> custom notification title text.
-b, --body <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 <https://unlicense.org/>
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

94
src/codes Executable file
View File

@ -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 '<Esc> character can be set as \\e or \\033 or \\x1B'
echo
echo -e '16 colors and formatting control sequence is <Esc>[ColorNumberm'
echo -e '256 foreground control sequence is <Esc>[38;5;ColorNumberm'
echo -e '256 background control sequence is <Esc>[48;5;1mColorNumberm'
echo
echo -e '<Esc>[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

138
src/currency Executable file
View File

@ -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 "/<table class/,\$d" | tac |
dos2unix | tr '\n' ' ' | sed 's%>\s*<%><%g' | sed 's%</tbody>%%g' |
sed 's%<tr>%\n%g;s%</tr>%%g' | sed 's%/%%g' |
awk -F '<td>' '{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] [<currency>] [<number>]\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

174
src/dots Executable file
View File

@ -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

88
src/git-mass-clone Executable file
View File

@ -0,0 +1,88 @@
#!/usr/bin/env bash
print_help() {
cat <<- EOF
Clone and pull remote git repositories.
Usage: $0 [-fchv] <arguments>
Options:
-f, --fetch-from <file> clone repositories listed in file.
-c, --chdir <dir> 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

22
src/github-starred-repos Executable file
View File

@ -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
}

110
src/http Executable file
View File

@ -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]
[<host>[:<port>]] [<port>] [<dir>]
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> host to bind. Default: 0.0.0.0
<port> port to bind. Default: 8000
<dir> 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

95
src/log Executable file
View File

@ -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] <log>
Options:
-t [<num>], -<num> 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 (<log>):
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

157
src/md2 Executable file
View File

@ -0,0 +1,157 @@
#!/usr/bin/env bash
_md2_help() {
cat <<- EOF
Convert Markdown to different formats.
Usage: md2 [-v|-h] <command> [args]..
md2 html <input> [<output>]
md2 man <input> [<output>]
md2 pdf <input> [<output>]
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 <input> 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} <input> [<output>]"
[ "$func" == "_md2man" ] && \
echo "Pass only <input> 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 "$@"

121
src/mega Executable file
View File

@ -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 <http://unlicense.org/>
_mega_help_wrapper() {
cat << 'EOF'
MEGAcmd wrapper script
___ ___ __ __ __
/' __` __`\ /'__`\ /'_ `\ /'__`\
/\ \/\ \/\ \/\ __//\ \L\ \/\ \L\.\_
\ \_\ \_\ \_\ \____\ \____ \ \__/.\_\
\/_/\/_/\/_/\/____/\/___L\ \/__/\/_/
/\____/
\_/__/
Usage: mega command [<options>] [<arguments>]
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}" "$@"

197
src/parts Executable file
View File

@ -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 <part>] [-l | --lang <language>]
[-t | --tag <tag>] [-d | --descr <description>]
[-r | --regex <regex>]
Options:
-i, --interactive add a part in interactive mode.
-a, --add <part> part text.
-l, --lang <language> part's language.
-t, --tag <tag> part's tag.
-d, --descr <description> part description
-r, --regex <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
}

47
src/rand Executable file
View File

@ -0,0 +1,47 @@
#!/usr/bin/env bash
_count=1
rand_help() {
cat <<- EOF
Print random array item.
Usage: rand [-vhc] <arguments>...
Options:
-c <count> print <count> 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

113
src/safeeject Executable file
View File

@ -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] [<mountpoint>]
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

31
src/unflac Executable file
View File

@ -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] [<file>]'
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

323
src/upload Executable file
View File

@ -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=<path>] [<user@server:/path>]
[<alias>] <files>...
Options:
-d, --dest=<path> 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:
<server alias> <user@server:/destination/dir> [<password>]
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"

70
src/view Executable file
View File

@ -0,0 +1,70 @@
#!/usr/bin/env bash
view_version=0.8
view_help() {
cat <<- EOF
Print highlighted text to STDOUT.
Usage: view [-vhpn] [<file>]
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"

89
src/yad Executable file
View File

@ -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] <link>
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"