44 Commits

Author SHA1 Message Date
fatalerrors
84e6fdd429 added entropy and password source to pwdscore 2026-04-01 18:15:11 +02:00
fatalerrors
8fe11776cb add --all-users to rmhost, hardening 2026-04-01 17:54:23 +02:00
fatalerrors
0737d0c647 reworked genpwd / introduce pwdscore 2026-04-01 17:52:09 +02:00
fatalerrors
d72fa1a712 hardening 2026-04-01 17:21:54 +02:00
fatalerrors
08e9e6c799 greatly improved upgrade system 2026-04-01 17:20:49 +02:00
fatalerrors
ac66e896dd exit -> return 2026-04-01 15:56:48 +02:00
fatalerrors
0712be626b wording and other minor improvments 2026-04-01 15:53:44 +02:00
fatalerrors
3f8b81562b sorted alias out 2026-04-01 15:53:10 +02:00
fatalerrors
96d1dc695d added missing function in help 2026-04-01 15:28:12 +02:00
fatalerrors
c039ab6ea0 improved and modernised file functions 2026-04-01 15:27:45 +02:00
fatalerrors
6b85556a53 typo 2026-03-26 17:19:06 +01:00
fatalerrors
cf9a85e61b update default conf with new values 2026-03-26 17:13:24 +01:00
fatalerrors
75e047d70e update help 2026-03-26 16:48:21 +01:00
fatalerrors
742ec484a7 made langage definition shortcuts configurable 2026-03-26 16:47:16 +01:00
fatalerrors
1b7262c0cd few minor update fix 2026-03-26 15:12:18 +01:00
fatalerrors
e387209c10 busy can properly exit, pattern can not inject code 2026-03-26 14:43:12 +01:00
fatalerrors
f5d59ec194 git parameter conflict, better matrix rendering, better speed unit handling 2026-03-25 18:11:01 +01:00
fatalerrors
2ee1c935ac make the rain able to be matrix 2026-03-25 17:48:27 +01:00
fatalerrors
e41c1a4c51 harden rmspc, better median calculation in file_stats 2026-03-25 16:10:41 +01:00
fatalerrors
60dfe19049 message type case unsensitive 2026-03-25 15:39:05 +01:00
fatalerrors
c32771a4ff default configuration updated 2026-03-25 15:28:08 +01:00
fatalerrors
080511d0bd add NO_COLOR support 2026-03-25 15:27:23 +01:00
fatalerrors
d8bdfefdf1 typo 2026-03-25 15:26:39 +01:00
fatalerrors
5f5f9c0e71 add trap protection, and option to force remplacement 2026-03-25 15:14:10 +01:00
fatalerrors
30387a4f08 update copyright info, uniformize sheebang 2026-03-25 14:39:58 +01:00
fatalerrors
0c51363d86 revert factorisation attempt 2026-03-25 14:37:20 +01:00
fatalerrors
043fbaef0b protect against code injection, interpret vars 2026-03-25 14:35:53 +01:00
fatalerrors
ed5587712e support for spaced filename 2026-03-25 14:19:08 +01:00
fatalerrors
58cc76c317 pre generate sections of the configuration file 2026-03-11 17:20:50 +01:00
fatalerrors
e82ee06e1d secured some implementation, check bash version 2026-03-11 11:41:56 +01:00
fatalerrors
bc8cb4a237 updated readme and final fixes for net.sh 2026-03-10 18:06:30 +01:00
fatalerrors
ae90a9f4c4 fix conf file comments, version bump 2026-03-10 17:46:30 +01:00
fatalerrors
7e661ca2de add dwl (downloader wrapper) and make use of it, fixed myextip 2026-03-10 17:45:26 +01:00
fatalerrors
9f22ed4304 fix show_as initialization 2026-03-10 15:23:52 +01:00
fatalerrors
1484b004be implemented showextip 2026-03-10 12:01:45 +01:00
fatalerrors
0a4206b890 fix getopt behavior 2026-03-10 11:40:37 +01:00
fatalerrors
02a1e25df2 fix profile.conf line endind 2026-03-10 11:02:31 +01:00
fatalerrors
7ca0a6fb88 fixed README, version bump 2026-03-10 10:55:02 +01:00
fatalerrors
25df408e37 implemented ini style parsed configuration file 2026-03-10 10:53:31 +01:00
fatalerrors
3eab1f98d5 fix parameter detection 2026-03-10 10:52:45 +01:00
fatalerrors
6c895b509a pursue --help implementation 2026-03-10 10:52:17 +01:00
fatalerrors
2ece711e1a huge longrun improvements 2026-03-06 17:46:26 +01:00
fatalerrors
39a7e7b40f version bump 2026-03-05 11:56:15 +01:00
fatalerrors
6d5d872b71 add a missing fi 2026-03-05 11:49:10 +01:00
21 changed files with 3027 additions and 919 deletions

View File

@@ -20,7 +20,8 @@ prompt. Here is a non-exhaustive list of what we have:
- A bar style prompt with hour, execution time and exit code of the last - A bar style prompt with hour, execution time and exit code of the last
command; command;
- clean: erase after confirmation any backup file, possibly recursively; - clean: erase after confirmation any backup file, possibly recursively;
- dpkgs: search for the given pattern in the installed packages name; - dwl: a curl/wget/fetch download wrapper;
- pkgs: search for the given pattern in the installed packages name;
- expandlist: usefull in scripts, it expand any expression using wildcards into - expandlist: usefull in scripts, it expand any expression using wildcards into
the corresponding list of file and directories; the corresponding list of file and directories;
- genpwd: generate one or more random secure password; - genpwd: generate one or more random secure password;
@@ -31,6 +32,7 @@ the corresponding list of file and directories;
- ku: kill all the processes owned by the given user name or ID; - ku: kill all the processes owned by the given user name or ID;
- mcd: create a directory and immediately move into it; - mcd: create a directory and immediately move into it;
- meteo: display weather forecast information; - meteo: display weather forecast information;
- myextip: get informations about your public IP;
- ppg: look for the given patern in the running processes; - ppg: look for the given patern in the running processes;
- rain: console screensaver with rain effect; - rain: console screensaver with rain effect;
- rmhost: remove the given host (name or IP) to the list of SSH known host; - rmhost: remove the given host (name or IP) to the list of SSH known host;
@@ -51,8 +53,9 @@ directory only if needed;
## 3. Configuration ## 3. Configuration
Some functions might have configurable default behaviour. You can create a Some functions might have configurable default behaviour. You can create a
.profile.conf file to configure those default behaviour. You should have a look profile.conf file to configure those default behaviour. You should have a look
at the doc/.profile.conf.example to see the list of available options. at the doc/profile.conf.example to see the list of available options. The
configuration file is located in the same directory as profile.sh file.
## 4. Contact and more information ## 4. Contact and more information
### 4.1. New users ### 4.1. New users

View File

@@ -7,6 +7,9 @@ Current version from Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
Version history: Version history:
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
# 05/03/2026 v3.6.1
Fix a typo in compress.sh
# 05/03/2026 v3.6.0 # 05/03/2026 v3.6.0
Improved utaz to make it multiformat with lot of it Improved utaz to make it multiformat with lot of it
Introduced ppu and ppn Introduced ppu and ppn

110
profile.conf Executable file
View File

@@ -0,0 +1,110 @@
[system]
# System section is used to set Bash behavior and other system related
# variables, such as the default pager, the terminal type, etc.
# Set bash history
HISTSIZE=50000
HISTIGNORE="&:[bf]g:exit"
# Set default pager
PAGER=less
# Set terminal colors behavior
TERM=xterm-256color
[compress]
# Section used by compress.sh
[debug]
# Section used by debug.sh
[disp]
# Section used by disp.sh
# Set to any value to disable colors in internal profile output (not controling binary output)
# NO_COLOR=1
[filefct]
# Section used by filefct.sh
[fun]
# Section used by fun.sh
[info]
# Section used by info.sh
# Default city for weather forcast and local news
DEFAULT_CITY="Toulouse"
[lang]
# Section used by lang.sh
# List of locale shortcuts to build, in the form "shortcut:locale,...".
# Generate a function setXX for each shortcut defined.
SET_LOCALE="fr:fr_FR.UTF-8,us:en_US.UTF-8"
[net]
# Section used by net.sh
[packages]
# Section used by packages.sh
[prompt]
# Section used by prompt.sh
[pwd]
# Section used by pwd.sh
[rain]
# Section used by rain.sh
[ssh]
# Section used by ssh.sh
[updates]
# Section used by updates.sh
[general]
# General section allow to set any variable that can be used by the user.
# It is also a good place to set freely global variables for personal use.
# Set some compiling values
CFLAGS="-O2 -pipe -march=native"
CXXFLAGS="$CFLAGS"
MAKEFLAGS='-j12'
PKGSOURCES='/share/src/archives'
[aliases]
# Aliases section is used to set user aliases, it is loaded only for
# interactive shells.
# Various ls aliases
ll='ls -laFh --color=auto'
la='ls -Ah --color=auto'
l='ls -CF --color=auto'
ls='ls --color=auto'
# Add color to grep output
grep='grep --color=auto'
egrep='egrep --color=auto'
fgrep='fgrep --color=auto'
# Quick find alias
qfind="find . -name "
# Some alias for compiling
mk='make'
mkck='make check'
mkin='make install'
mkdin='make DESTDIR=$PWD/dest-install install'
# ssh alias with X11 forwarding, without right restriction
ssh='ssh -Y'
# Resume mode for wget
wget='wget -c' # resume mode by default
# Human readable by default
df='df -H'
du='du -ch'
sdu='du -sk ./* | sort -n'
hdu='du -hs ./* | sort -H'
# Readable dmesg timestamps
dmesg='dmesg -T'
# End of profile.conf

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -35,8 +35,13 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Smartly uncompress archives (zip only for now) # Smartly uncompress archives
# ------------------------------------------------------------------------------ # Usage: utaz [option] [directorie(s)|file(s)]
# Options:
# -h, --help Display that help screen
# -d, --delete If decompression succeeded, delete the source file
# -c, --create-dir Always create a host directory
# -n, --no-dir Never create a host directory
utaz() utaz()
{ {
_ununzip() _ununzip()
@@ -136,54 +141,89 @@ utaz()
rpm2cpio "$1" | (cd "$2/" && cpio -idmv) >/dev/null 2>&1 rpm2cpio "$1" | (cd "$2/" && cpio -idmv) >/dev/null 2>&1
} }
for opt in $@; do local PARSED=$(getopt -o hdcn --long help,delete,create-dir,no-dir -n 'utaz' -- "$@")
case ${opt} in
"-h" | "--help") if [ $? -ne 0 ]; then
echo "utaz: uncompress all the given files and/or the ones found in the given" disp E "Invalid options, use \"utaz --help\" to display usage."
echo " directories creating an host directory where needed." return 1
echo fi
echo "Usage: utaz [option] [directorie(s)|file(s)]" eval set -- "$PARSED"
echo while true; do
echo "Options:" case "$1" in
echo " -h, --help Display that help screen" -h|--help)
echo " -d, --delete If decompression succeeded, delete the source file" printf "utaz: uncompress all the given files and/or the ones found in the given\n"
echo " -c, --create-dir Always create a host directory" printf " directories creating an host directory where needed.\n\n"
echo " -n, --no-dir Never create a host directory" printf "Usage: utaz [option] [directorie(s)|file(s)]\n\n"
echo printf "Options:\n"
printf "\t-h, --help\t\tDisplay that help screen\n"
printf "\t-d, --delete\t\tIf decompression succeeded, delete the source file\n"
printf "\t-c, --create-dir\tAlways create a host directory\n"
printf "\t-n, --no-dir\t\tNever create a host directory\n\n"
printf "Supported archive format:\n"
printf "\t- zip\n"
printf "\t- tar.gz, .tgz\n"
printf "\t- tar.bz2, .tbz2\n"
printf "\t- tar.xz, .txz\n"
printf "\t- tar.lz, .tlz\n"
printf "\t- rar\n"
printf "\t- arj\n"
printf "\t- lha, lzh\n"
printf "\t- ace\n"
printf "\t- 7z, p7z\n"
printf "\t- zst\n"
printf "\t- cpio\n"
printf "\t- cab\n"
printf "\t- deb\n"
printf "\t- rpm\n"
return 0 return 0
;; ;;
-d|--delete)
"-d" | "--delete")
local willrm=1 local willrm=1
shift
;; ;;
-c|--create-dir)
"-c" | "--create-dir")
local createdir=1 local createdir=1
shift
;; ;;
-n|--no-dir)
"-n" | "--no-dir")
local nodir=1 local nodir=1
shift
;; ;;
--)
"-"*) shift
disp E "Invalid option, use \"utaz --help\" to display options list" break
echo
return 1
;; ;;
*) *)
# The ${opt%/} writing is to remove trailing / if any disp E "Invalid option, use \"utaz --help\" to display options list"
local LIST="${LIST} ${opt%/}" return 1
;; ;;
esac esac
done done
# The remaining arguments after -- are the files/directories to process,
# "." is used if none is given
local FILES=("$@")
[[ ${#FILES[@]} -eq 0 ]] && FILES=(".")
[[ -n ${createdir} && -n ${nodir} ]] && \ [[ -n ${createdir} && -n ${nodir} ]] && \
disp E "The --create-dir and --no-dir options are mutually exclusive." disp E "The --create-dir and --no-dir options are mutually exclusive."
[[ -z ${LIST} ]] && local LIST="." for zitem in "${FILES[@]}"; do
for zitem in ${LIST}; do # Build list of input files to process, with whitespace-safe handling.
for f in "${zitem}"/*; do local targets=()
if [[ -f "$zitem" ]]; then
targets=("$zitem")
elif [[ -d "$zitem" ]]; then
mapfile -d '' -t targets < <(find "$zitem" -mindepth 1 -maxdepth 1 -print0 2>/dev/null)
if [[ ${#targets[@]} -eq 0 ]]; then
disp I "Directory ${zitem} is empty, skipping."
continue
fi
else
disp W "Path ${zitem} is not a file or directory, skipping."
continue
fi
for f in "${targets[@]}"; do
local dir="${f%.*}" local dir="${f%.*}"
local extractor="" local extractor=""
case "$f" in case "$f" in
@@ -193,13 +233,13 @@ utaz()
*.tar.gz|*.tgz) *.tar.gz|*.tgz)
extractor="_ungzip" extractor="_ungzip"
;; ;;
*.tar.bz2) *.tar.bz2|*.tbz2)
extractor="_unbzip2" extractor="_unbzip2"
;; ;;
*.tar.xz) *.tar.xz|*.txz)
extractor="_unxz" extractor="_unxz"
;; ;;
*.tar.lz) *.tar.lz|*.tlz)
extractor="_unlzop" extractor="_unlzop"
;; ;;
*.tar) *.tar)
@@ -217,7 +257,7 @@ utaz()
*.ace) *.ace)
extractor="_ununace" extractor="_ununace"
;; ;;
*.7z) *.7z|*.p7z)
extractor="_un7z" extractor="_un7z"
;; ;;
*.zst) *.zst)
@@ -244,24 +284,25 @@ utaz()
# Verify binary existence # Verify binary existence
local cmd=${extractor//_un/} local cmd=${extractor//_un/}
if [[ $cmd == "deb" ]]; then if [[ $cmd == "deb" ]]; then
command -v dpkg-deb >/dev/null 2>&1 || { command -v -- dpkg-deb >/dev/null 2>&1 || {
disp E "The program 'dpkg-deb' is not installed, aborting." disp E "The program 'dpkg-deb' is not installed, aborting."
continue continue
} }
elif [[ $cmd == "rpm" ]]; then elif [[ $cmd == "rpm" ]]; then
command -v rpm2cpio >/dev/null 2>&1 || { command -v -- rpm2cpio >/dev/null 2>&1 || {
disp E "The program 'rpm2cpio' is not installed, aborting." disp E "The program 'rpm2cpio' is not installed, aborting."
continue continue
} }
command -v cpio >/dev/null 2>&1 || { command -v -- cpio >/dev/null 2>&1 || {
disp E "The program 'cpio' is not installed, aborting." disp E "The program 'cpio' is not installed, aborting."
continue continue
} }
else else
command -v ${cmd} >/dev/null 2>&1 || { command -v -- "${cmd}" >/dev/null 2>&1 || {
disp E "Binary ${cmd} necessary to extract ${f} is missing." disp E "Binary ${cmd} necessary to extract ${f} is missing."
continue continue
} }
fi
disp I "Processing archive ${f} with ${extractor}..." disp I "Processing archive ${f} with ${extractor}..."
mkdir -p "${dir}" mkdir -p "${dir}"
@@ -276,10 +317,14 @@ utaz()
rm -f "${f}" && disp I "File ${zitem}/${f} deleted." rm -f "${f}" && disp I "File ${zitem}/${f} deleted."
;; ;;
1) 1)
disp W "Compression program returned a warning: deletion canceled." if [[ -n ${willrm} ]]; then
disp W "Compression program returned a warning: deletion canceled."
else
disp W "Compression program returned a warning."
fi
;; ;;
*) *)
disp E "The zip file seems corrupted, failed." disp E "The compressed file ${f} seems corrupted, failed."
rm -rf "${dir}" >/dev/null 2>&1 rm -rf "${dir}" >/dev/null 2>&1
continue continue
;; ;;
@@ -288,7 +333,12 @@ utaz()
if [[ -n ${createdir} ]]; then if [[ -n ${createdir} ]]; then
disp I "Archive extracted successfully in subdirectory." disp I "Archive extracted successfully in subdirectory."
elif [[ -n ${nodir} ]]; then elif [[ -n ${nodir} ]]; then
mv "./${dir}/"* ./ && rmdir "${dir}" shopt -s nullglob
for child in "${dir}"/*; do
mv -- "$child" .
done
shopt -u nullglob
rmdir -- "${dir}"
disp I "Archive extracted successfully, no subdirectory needed." disp I "Archive extracted successfully, no subdirectory needed."
else else
# Set nullglob to ensure the array is empty if no files match # Set nullglob to ensure the array is empty if no files match
@@ -298,7 +348,12 @@ utaz()
# Check if exactly one item exists and if that item is a directory # Check if exactly one item exists and if that item is a directory
if [[ ${#contents[@]} -eq 1 ]] && [[ -d "${contents[0]}" ]]; then if [[ ${#contents[@]} -eq 1 ]] && [[ -d "${contents[0]}" ]]; then
# Single directory detected # Single directory detected
mv "${contents[0]}"/* ./ && rmdir "${dir}" shopt -s nullglob
for child in "${contents[0]}"/*; do
mv -- "$child" .
done
shopt -u nullglob
rmdir -- "${dir}"
disp I "Archive extracted successfully, no subdirectory needed." disp I "Archive extracted successfully, no subdirectory needed."
else else
disp I "Archive extracted successfully in subdirectory." disp I "Archive extracted successfully in subdirectory."
@@ -309,10 +364,21 @@ utaz()
done done
} }
export -f utaz export -f utaz
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Compress directories or files into one or more archive # Compress directories or files into one or more archive
# ------------------------------------------------------------------------------ # Usage: taz [option] [--parallel=<n>] [--format=<format>] [directory1 ... directoryN]
# Options:
# -h, --help Display that help screen
# -d, --delete Delete source file or directory after success
# -f, --format Chose archive format in the given list. If several format are
# given, the smalest is kept
# -p, --parallel Number of threads to use (if allowed by underlying utility)
# -v, --verbose Display progress where possible
# -q, --quiet Display less messages (only errors and warnings)
# -1, .., -9 Compression level to use [1=fast/biggest, 9=slow/smallest]
taz() taz()
{ {
_doxz() _doxz()
@@ -420,78 +486,92 @@ taz()
return $? return $?
} }
for opt in $@; do local PARSED
case $opt in PARSED=$(getopt -o hdf:p:vq123456789 --long help,delete,format:,parallel:,verbose,quiet --name "taz" -- "$@")
"-h" | "--help") if [ $? -ne 0 ]; then
echo "taz: archive all files of a directory." disp E "Invalid options, use \"taz --help\" to display usage."
echo return 1
echo "Usage: taz [option] [--parallel=<n>] [--format=<format>] [directory1 ... directoryN]" fi
echo eval set -- "$PARSED"
echo "Options:" while true; do
echo " -h, --help Display that help screen" case "$1" in
echo " -d, --delete Delete source file or directory after success" -h|--help)
echo " -f, --format Chose archive format in the given list. If several format are" printf "taz: archive all files of a directory.\n\n"
echo " given, the smalest is kept" printf "Usage: taz [option] [--parallel=<n>] [--format=<format>] [directory1 ... directoryN]\n\n"
echo " -p, --parallel Number of threads to use (if allowed by underlying utility)" printf "Options:\n"
echo " -v, --verbose Display progress where possible" printf "\t-h, --help\tDisplay that help screen\n"
echo " -q, --quiet Display less messages (only errors and warnings)" printf "\t-d, --delete\tDelete source file or directory after success\n"
echo " -1, .., -9 Compression level to use [1=fast/biggest, 9=slow/smallest]" printf "\t-f, --format\tChose archive format in the given list. If several format are"
echo printf "\t\t\tgiven, the smalest is kept\n"
echo "Supported archive format:" printf "\t-p, --parallel\tNumber of threads to use (if allowed by underlying utility)\n"
echo " Param.| programs | Algo. | Description" printf "\t-v, --verbose\tDisplay progress where possible\n"
echo " ------+---------------+-------+----------------------------------------" printf "\t-q, --quiet\tDisplay less messages (only errors and warnings)\n"
echo " lz | plzip, lzip | lzma | Safe efficient default format" printf "\t-1, .., -9\tCompression level to use [1=fast/biggest, 9=slow/smallest]\n\n"
echo " xz | xz | lzma2 | Unsafe, not for long term" printf "Supported archive format:\n"
echo " bz2 | pbzip2, bzip2 | bzip2 | Historical but less efficient than lz" printf "\tParam.| programs | Algo. | Description\n"
echo " gz | pigz, gzip | lz77 | Historical, safe, fast" printf "\t------+---------------+-------+----------------------------------------\n"
echo " lzo | lzop | lzo | Very fast but no multithread" printf "\t lz | plzip, lzip | lzma | Safe efficient default format\n"
echo " tar | tar | tar | No compression" printf "\t xz | xz | lzma2 | Unsafe, not for long term\n"
echo printf "\t bz2 | pbzip2, bzip2 | bzip2 | Historical but less efficient than lz\n"
printf "\t gz | pigz, gzip | lz77 | Historical, safe, fast\n"
printf "\t lzo | lzop | lzo | Very fast but no multithread\n"
printf "\t tar | tar | tar | No compression\n"
printf "\n"
return 0 return 0
;; ;;
"-d" | "--delete") -d|--delete)
local willrm=1 local willrm=1
shift
;; ;;
"-f"?* | "--format"?*) -f|--format)
local compform=$(echo "$opt" | cut -f 2- -d '=') local compform=$2
shift 2
;; ;;
"-p"?* | "--parallel"?*) -p|--parallel)
local nproc=$(echo "$opt" | cut -f 2- -d '=') local nproc=$2
shift 2
;; ;;
"-v" | "--verbose") -v|--verbose)
local verbose=1 local verbose=1
shift
;; ;;
"-q" | "--quiet") -q|--quiet)
local quiet=1 local quiet=1
shift
;; ;;
"-"*) -[1-9])
local complevel=$(echo $opt | sed 's/-//') compression="${1#-}"
if ! [[ $complevel =~ ^[1-9]+$ ]]; then shift
disp E "Invalid option, use taz --help to display options list" ;;
echo --)
return 1 shift
fi break
;; ;;
*) *)
local LIST="$LIST ${opt%/}" disp E "Invalid option, use \"taz --help\" to display options list"
return 1
;; ;;
esac esac
done done
# The remaining arguments after -- are the files/directories to process,
# "." is used if none is given
local FILES=("$@")
[[ ${#FILES[@]} -eq 0 ]] && FILES=(".")
[[ ! $compform ]] && compform=lz # safe and efficient (unless data are already compressed) [[ ! $compform ]] && compform=lz # safe and efficient (unless data are already compressed)
[[ ! $nproc ]] && nproc=1 [[ ! $nproc ]] && nproc=1
[[ ! $complevel ]] && complevel=6 [[ ! $complevel ]] && complevel=6
[[ $verbose -gt 1 && $quiet -gt 1 ]] && [[ $verbose -gt 1 && $quiet -gt 1 ]] &&
disp E "The --verbose and --quiet options can't be used together." disp E "The --verbose and --quiet options can't be used together."
for item in $LIST; do for item in "${FILES[@]}"; do
local donetar=0 local donetar=0
disp I "Processing $item..." disp I "Processing $item..."
@@ -536,6 +616,7 @@ taz()
unset quiet unset quiet
} }
export -f taz export -f taz
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -36,27 +36,22 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Display a backtrace # Display a backtrace
# ------------------------------------------------------------------------------ # Usage: backtrace
function backtrace() function backtrace()
{ {
echo "========= Call stack =========" printf "========= Call stack =========\n"
typeset -i i=0 local i=1 # We begin at 1 to ignore backtrace itself
while [[ $i -lt ${#FUNCNAME[@]} ]]; do
local func= printf '%15s() %s:%d\n' \
for func in "${FUNCNAME[@]}"; do "${FUNCNAME[$i]}" "${BASH_SOURCE[$i]}" "${BASH_LINENO[$(( i-1 ))]}"
if [[ $i -ne 0 ]]; then ((i++))
printf '%15s() %s:%d\n' \
"$func" "${BASH_SOURCE[$i]}" "${BASH_LINENO[(($i - 1))]}"
fi
let i++ || true
done done
unset func i unset i
echo "==============================" printf "==============================\n"
} }
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Function to be trapped for errors investigation # Function to be trapped for errors investigation
# ------------------------------------------------------------------------------
function error() function error()
{ {
local errcode=$? local errcode=$?
@@ -66,45 +61,70 @@ function error()
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Activate or deactivate error trapping to display backtrace # Activate or deactivate error trapping to display backtrace
# ------------------------------------------------------------------------------ # Usage: settrace <--on|--off|--status>
settrace() settrace()
{ {
local status="off" local status="off"
[[ $(trap -p ERR) ]] && status="on" [[ $(trap -p ERR) ]] && status="on"
#trap -p ERR #trap -p ERR
for opt in $@; do
case $opt in local PARSED
"-h" | "--help") PARSED=$(getopt -oh --long help,on,off,status,force -- "$@")
echo "Try to activate backtrace display for script debugging." if [[ $? -ne 0 ]]; then
echo disp E "Invalid options, use \"settrace --help\" to display usage."
echo "Options:" return 1
echo " --on Activate backtrace generation" fi
echo " --off Deactivate backtrace generation" eval set -- "$PARSED"
echo local force=0
echo "That function active a trap event on error. If the script you want to" while true; do
echo "debug overload the ERR bash trap, it will not work." case $1 in
echo -h|--help)
printf "Try to activate backtrace display for script debugging.\n\n"
printf "Options:\n"
printf "\t--on\t\tActivate backtrace generation\n"
printf "\t--force\t\tForce replacement of existing trap (use with --on)\n"
printf "\t--off\t\tDeactivate backtrace generation\n\n"
printf "That function active a trap event on error. If the script you want to\n"
printf "debug overload the ERR bash trap, it will not work.\n"
return 0
;; ;;
"--on") --on)
if [[ $status == "on" ]]; then if [[ ${status} == "on" ]] && [[ $force -eq 0 ]]; then
disp W "ERR signal trap is already set, replacing previous trap!" disp E "ERR signal trap is already set. Use --force to replace it."
return 1
fi fi
trap "error" ERR trap "error" ERR
shift
;; ;;
"--off") --force)
if [[ $status != "on" ]]; then force=1
shift
;;
--off)
if [[ ${status} != "on" ]]; then
disp W "ERR signal trap is already unset!" disp W "ERR signal trap is already unset!"
fi fi
trap - ERR trap - ERR
shift
;; ;;
"--status") --status)
disp "ERR trap signal is ${status}." disp I "Trap signal is ${status}."
shift
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"settrace --help\" to display usage."
return 1
;; ;;
esac esac
done done
unset status unset status force
} }
export -f settrace export -f settrace
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -36,11 +36,10 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Color definitions # Color definitions
# ------------------------------------------------------------------------------
# Standard 16 colors display declaration # Standard 16 colors display declaration
export DEFAULTFG="\e[0;39m" export DEFAULTFG='\e[0;39m'
export DEFAULTBG="\e[0;49m" export DEFAULTBG='\e[0;49m'
export DEFAULTCOL=${DEFAULTBG}${DEFAULTFG} export DEFAULTCOL="${DEFAULTBG}${DEFAULTFG}"
export RESETCOL=$'\e[0m' export RESETCOL=$'\e[0m'
# Regular Colors # Regular Colors
@@ -112,31 +111,58 @@ export On_IBlue='\e[0;104m'
export On_IPurple='\e[0;105m' export On_IPurple='\e[0;105m'
export On_ICyan='\e[0;106m' export On_ICyan='\e[0;106m'
export On_IWhite='\e[0;107m' export On_IWhite='\e[0;107m'
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Display a message # Display a message
# ------------------------------------------------------------------------------ # Usage: disp <type> <message>
# Types:
# I : info (green)
# W : warning (yellow)
# E : error (red)
# D : debug (cyan)
disp() disp()
{ {
case $1 in # Handle NO_COLOR: disable colors if set
local color_enabled=1
[[ -n $NO_COLOR ]] && color_enabled=0
case ${1^^} in
"I") "I")
local heads="[ ${IGreen}info${DEFAULTFG} ]" if [[ $color_enabled -eq 1 ]]; then
local heads="[ ${IGreen}info${DEFAULTFG} ]"
else
local heads="[ info ]"
fi
shift shift
[[ -z $QUIET || $QUIET -ne 1 ]] && \ [[ -z $QUIET || $QUIET -ne 1 ]] && \
printf "%b\n" "${heads} $*${RESETCOL}" printf "%b\n" "${heads} $*${RESETCOL}"
;; ;;
"W") "W")
local heads="[ ${IYellow}Warning${DEFAULTFG} ]" if [[ $color_enabled -eq 1 ]]; then
local heads="[ ${IYellow}Warning${DEFAULTFG} ]"
else
local heads="[ Warning ]"
fi
shift shift
printf "%b\n" "${heads} $*${RESETCOL}" >&2 printf "%b\n" "${heads} $*${RESETCOL}" >&2
;; ;;
"E") "E")
local heads="[ ${IRed}ERROR${DEFAULTFG} ]" if [[ $color_enabled -eq 1 ]]; then
local heads="[ ${IRed}ERROR${DEFAULTFG} ]"
else
local heads="[ ERROR ]"
fi
shift shift
printf "%b\n" "${heads} $*${RESETCOL}" >&2 printf "%b\n" "${heads} $*${RESETCOL}" >&2
;; ;;
"D") "D")
local heads="[ ${ICyan}debug${DEFAULTFG} ]" if [[ $color_enabled -eq 1 ]]; then
local heads="[ ${ICyan}debug${DEFAULTFG} ]"
else
local heads="[ debug ]"
fi
shift shift
[[ -n $DEBUG && $DEBUG -gt 1 ]] && \ [[ -n $DEBUG && $DEBUG -gt 1 ]] && \
printf "%b\n" "${heads} $*${RESETCOL}" printf "%b\n" "${heads} $*${RESETCOL}"
@@ -149,4 +175,9 @@ disp()
} }
export -f disp export -f disp
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Load disp section variables
load_conf disp
# EOF # EOF

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -35,223 +35,426 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# expandlist : treat wildcards in a file/directory list # Expand wildcards in a file/directory list and quote the results
# ------------------------------------------------------------------------------ # Usage: expandlist [options] <item1 [item2 ... itemN]>
expandlist() expandlist()
{ {
local result="" local separator=" "
for item in "$1"; do local PARSED
for content in "$item"; do
result+="\"$content\" "
done
done
echo $result
}
# ------------------------------------------------------------------------------ PARSED=$(getopt -o hs:n --long help,separator:,newline -n 'expandlist' -- "$@")
# Clean a directory or a tree from temporary or backup files if [[ $? -ne 0 ]]; then
# ------------------------------------------------------------------------------ disp E "Invalid options, use \"expandlist --help\" to display usage."
clean() return 1
{ fi
for opt in $@; do eval set -- "$PARSED"
case $opt in
"-r" | "--recurs")
local recursive=1
;;
"-h" | "--help") while true; do
echo "clean: erase backup files in the given directories." case "$1" in
echo -h|--help)
echo "Usage: clean [option] [directory1] [...[directoryX]]" printf "expandlist: expand globs and wrap matched items in double quotes.\n\n"
echo printf "Usage: expandlist [options] <item1 [item2 ... itemN]>\n\n"
echo "Options:" printf "Options:\n"
echo " -h, --help Display that help screen" printf "\t-h, --help\t\tDisplay this help screen\n"
echo " -r, --recurs Do a recursive cleaning" printf "\t-s, --separator SEP\tSet output separator (default: space)\n"
echo " -f, --force Do not ask for confirmation (use with care)" printf "\t-n, --newline\t\tUse a newline as separator\n"
echo " -s, --shell Do nothing and display what will be executed" return 0
echo ;;
return 0 -s|--separator)
;; separator="$2"
shift 2
"-s" | "--shell") ;;
local outshell=1 -n|--newline)
;; separator=$'\n'
shift
"-f" | "--force") ;;
local force=1 --)
;; shift
break
"-"*) ;;
disp E "Invalid option, use \"clean --help\" to display usage." *)
echo disp E "Invalid options, use \"expandlist --help\" to display usage."
return 1 return 1
;; ;;
*)
local dirlist="$dirlist $opt"
;;
esac esac
done done
[[ ! $dirlist ]] && local dirlist=$(pwd) local item="" result="" matched=0
shopt -s nullglob
[[ ! $recursive ]] && local findopt="-maxdepth 1" for item in "$@"; do
[[ ! $force ]] && local rmopt="-i" local expanded=()
unset recursive force
for dir in $dirlist; do # True glob expansion when wildcards are present.
local dellist=$(find "$dir" $findopt -type f -name "*~" -o -name "#*#" \ if [[ "$item" == *'*'* || "$item" == *'?'* || "$item" == *'['* ]]; then
-o -name "*.bak" -o -name ".~*#") expanded=( $item )
for f in $dellist; do else
if [[ ! $outshell ]]; then expanded=( "$item" )
rm $rmopt $f fi
else
echo "rm $rmopt $f" if [[ ${#expanded[@]} -eq 0 ]]; then
continue
fi
for content in "${expanded[@]}"; do
if (( matched )); then
result+="$separator"
fi fi
result+="\"$content\""
matched=1
done done
done done
unset outshell dirlist dellist findopt rmopt
shopt -u nullglob
printf '%s\n' "$result"
}
export -f expandlist
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Clean a directory tree from temporary or backup files
# Usage: clean [options] [directory1] [...[directoryX]]
# Options:
# -h, --help: display help screen
# -r, --recurs: do a recursive cleaning
# -f, --force: do not ask for confirmation (use with care)
# -s, --shell: do nothing and display what will be executed
clean()
{
local recursive=0 force=0 outshell=0
# Define short and long options
local PARSED
PARSED=$(getopt -o hrsf --long help,recurs,shell,force -n 'clean' -- "$@")
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"clean --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-r|--recurs)
recursive=1
shift
;;
-h|--help)
printf "clean: erase backup files in the given directories.\n\n"
printf "Usage: clean [option] [directory1] [...[directoryX]]\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay that help screen\n"
printf "\t-r, --recurs\t\tDo a recursive cleaning\n"
printf "\t-f, --force\t\tDo not ask for confirmation (use with care)\n"
printf "\t-s, --shell\t\tDo nothing and display what will be executed\n"
printf "\n"
return 0
;;
-s|--shell)
outshell=1
shift
;;
-f|--force)
force=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid parameter, use \"clean --help\" to display options list"
return 1
;;
esac
done
# Handle remaining arguments as directories
local dirlist=("$@")
[[ ${#dirlist[@]} -eq 0 ]] && dirlist=(".")
local findopt=() rmopt=()
(( ! recursive )) && findopt=(-maxdepth 1)
(( ! force )) && rmopt=(-i)
for dir in "${dirlist[@]}"; do
find "$dir" "${findopt[@]}" -type f \( -name "*~" -o -name "#*#" -o -name "*.bak" -o -name ".~*#" \) -print0 |
while IFS= read -r -d '' f; do
if (( outshell )); then
if (( ${#rmopt[@]} )); then
printf 'rm %s -- "%s"\n' "${rmopt[*]}" "$f"
else
printf 'rm -- "%s"\n' "$f"
fi
else
rm "${rmopt[@]}" -- "$f"
fi
done
done
} }
export -f clean export -f clean
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Create a directory then goes inside # Create a directory then goes inside
# ------------------------------------------------------------------------------ # Usage: mcd <directory>
mcd() mcd()
{ {
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
printf "mcd: Create a directory and enter it.\n\n"
printf "Usage: mcd <directory>\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
return 0
fi
if [[ ! $# -eq 1 ]]; then if [[ ! $# -eq 1 ]]; then
disp E "Create a directory then goes inside." disp E "Missing parameter. Use \"mcd --help\" to display usage."
disp E "Usage: mcd <directory>"
return 1 return 1
fi fi
mkdir -pv "$1" && cd "$1" || echo "Failed create or change directory." mkdir -pv "$1" && cd "$1" || {
printf "Failed create and/or change directory.\n"
return 1
}
} }
export -f mcd export -f mcd
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Rename all files in current directory to replace spaces with _ # Rename files and directories to replace spaces with another character
# ------------------------------------------------------------------------------ # Usage: rmspc [options]
# Options:
# -h, --help: display help screen
# -r, --recursive: treat subdirectories of the given directory
# -c, --subst-char: change the replacement character (default is underscore)
# -v, --verbose: display more details (recursive mode only)
# -s, --shell: do nothing and display commands that would be executed
rmspc() rmspc()
{ {
local lst="" local recurs=0 verb=0 shell=0
for opt in $@; do local substchar="_" substchar_set=0
case $opt in local mvopt=()
"-h" | "--help") local PARSED
echo "rmspc: remove spaces from all filenames in current directories"
echo
echo "Usage: rmspc [option]"
echo
echo "Options:"
echo " -h, --help Display that help screen"
echo " -r, --recursive Treat subdirectories of the given directory"
echo " -c, --subst-char Change the replacement character (default is underscore)"
echo " -v, --verbose Display more details (recursive mode only)"
echo " -s, --shell Do nothing and display commands that would be executed"
echo
echo "Note: if the --subst-char option is given without parameters, spaces will be"
echo " replaced with nothing (concatenation)."
echo
return 0
;;
"-r" | "--recursive") PARSED=$(getopt -o hr:c::vs --long help,recursive,subst-char::,verbose,shell -n 'rmspc' -- "$@")
local recurs=1 if [[ $? -ne 0 ]]; then
;; disp E "Invalid options, use \"rmspc --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
"-c"?* | "--subst-char"?*) while true; do
if [[ $(echo $opt | grep "=") ]]; then case "$1" in
local substchar=$(echo "$opt" | cut -f 2- -d '=') -h|--help)
else printf "rmspc: remove spaces from all filenames in current directories\n\n"
local substchar='none' printf "Usage: rmspc [option]\n\n"
fi printf "Options:\n"
;; printf "\t-h, --help\t\tDisplay that help screen\n"
printf "\t-r, --recursive\t\tTreat subdirectories of the given directory\n"
"-v" | "--verbose") printf "\t-c, --subst-char\tChange the replacement character (default is underscore)\n"
local verb=1 printf "\t-v, --verbose\t\tDisplay more details (recursive mode only)\n"
;; printf "\t-s, --shell\t\tDo nothing and display commands that would be executed\n\n"
printf "Note: if the --subst-char option is given without parameters, spaces will be\n"
"-s" | "--shell") printf " replaced with nothing (concatenation).\n"
local shell=1 return 0
;; ;;
-r|--recursive)
*) recurs=1
disp E "Invalid parameter, use \"rmspc --help\" to display options list" shift
echo ;;
return 1 -c|--subst-char)
;; substchar_set=1
substchar="$2"
shift 2
;;
-v|--verbose)
verb=1
shift
;;
-s|--shell)
shell=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid parameter, use \"rmspc --help\" to display options list"
return 1
;;
esac esac
done done
[[ ! $substchar ]] && substchar="_" [[ "$substchar" == "none" ]] && substchar=""
[[ $substchar == "none" ]] && local substchar="" (( verb )) && mvopt=(-v)
[[ $verb ]] && local mvopt="-v"
shopt -s nullglob
for f in *; do for f in *; do
[[ $recurs ]] && [[ -d "$f" ]] && ( if (( recurs )) && [[ -d "$f" ]]; then
[[ $verb ]] && disp I "Entering directory $(pwd)/$f ..." (
local lastdir=$f local lastdir=$f
pushd "$f" >/dev/null (( verb )) && disp I "Entering directory $(pwd)/$f ..."
rmspc $@ pushd "$f" >/dev/null || return 1
popd >/dev/null
[[ $verb ]] && disp I "Leaving directory $(pwd)/$lastdir"
unset lastdir
)
if [[ $(echo $f | grep " ") ]]; then if (( substchar_set )); then
rmspc ${recurs:+-r} -c "$substchar" ${verb:+-v} ${shell:+-s}
else
rmspc ${recurs:+-r} ${verb:+-v} ${shell:+-s}
fi
popd >/dev/null || return 1
(( verb )) && disp I "Leaving directory $(pwd)/$lastdir"
)
fi
if [[ "$f" == *" "* ]]; then
local newf="${f// /${substchar}}" local newf="${f// /${substchar}}"
local command="mv $mvopt \"$f\" \"$newf\"" [[ "$f" == "$newf" ]] && continue
if [[ $shell ]]; then if (( shell )); then
echo $command if (( ${#mvopt[@]} )); then
printf 'mv %s -- "%s" "%s"\n' "${mvopt[*]}" "$f" "$newf"
else
printf 'mv -- "%s" "%s"\n' "$f" "$newf"
fi
else else
$command mv "${mvopt[@]}" -- "$f" "$newf" || {
disp E "Failed renaming \"$f\" to \"$newf\"."
continue
}
fi fi
fi fi
done done
unset lst substchar verb shell newf command mvopt shopt -u nullglob
} }
export -f rmspc export -f rmspc
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# display stats about a file structure # Display statistics about a file tree
# ------------------------------------------------------------------------------ # Usage: file_stats [options] [path]
# Options:
# -H, --human Human readable sizes\n"
# -d, --details Display details (min/max/average/median)
# -m, --average Display only average size
# -M, --median Display only median size
# -c, --count Display only count of files
# -t, --total Display only total size
# -a, --all Display all stats in human readable format (shortcut for -H -d)
# -x, --ext [ext] Filter by extension (e.g. -x log for .log files)
# -X, --ext-list [list] Filter by multiple extensions (e.g. -X log,txt)
# --min [size] Minimum size (e.g., 10M)
# --max [size] Maximum size (e.g., 100M)
file_stats() file_stats()
{ {
local human=0 details=0 only_avg=0 only_med=0 only_count=0 only_total=0 local human=0 details=0 only_avg=0 only_med=0 only_count=0 only_total=0
local path="." show_all=1 ext_filter="" ext_list="" min_size="" max_size="" local path="." show_all=1 ext_filter="" ext_list="" min_size="" max_size=""
local OPTIND opt
# Analyse options local PARSED
while [[ "$1" =~ ^- ]]; do # Short: H, d, m, M, c, t, a, x:, X:
# Long: human, details, average, median, count, total, all, ext:, ext-list:, min:, max:, help
PARSED=$(getopt -o HdmMctax:X:h --long human,details,average,median,count,total,all,ext:,ext-list:,min:,max:,help -n 'file_stats' -- "$@")
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"file_stats --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in case "$1" in
-H) human=1 ;; -h|--help)
-d) details=1 ;; printf "Usage: file_stats [options] [path]\n\n"
-m) only_avg=1; show_all=0 ;; printf "Options:\n"
-M) only_med=1; show_all=0 ;; printf "\t-H, --human\t\tHuman readable sizes\n"
-c) only_count=1; show_all=0 ;; printf "\t-d, --details\t\tShow detailed histogram\n"
-t) only_total=1; show_all=0 ;; printf "\t-m, --average\t\tShow only average size\n"
-a) human=1; details=1 ;; printf "\t-M, --median\t\tShow only median size\n"
-x) ext_filter="${2#.}"; shift ;; printf "\t-c, --count\t\tShow only file count\n"
-X) ext_list="${2}"; shift ;; printf "\t-t, --total\t\tShow only total size\n"
--min) min_size="$2"; shift ;; printf "\t-a, --all\t\tShow all (human + details)\n"
--max) max_size="$2"; shift ;; printf "\t-x, --ext [ext]\t\tFilter by extension\n"
--) shift; break ;; printf "\t-X, --ext-list [list]\tFilter by comma-separated list\n"
-*) echo "Usage: file_stats [-h] [-d] [-mMctaxX --min N --max N] [path]"; return 1 ;; printf "\t--min [size]\t\tMinimum size (e.g., 10M)\n"
printf "\t--max [size]\t\tMaximum size (e.g., 100M)\n"
return 0 ;;
-H|--human)
human=1
shift
;;
-d|--details)
details=1
shift
;;
-m|--average)
only_avg=1
show_all=0
shift
;;
-M|--median)
only_med=1
show_all=0
shift
;;
-c|--count)
only_count=1
show_all=0
shift
;;
-t|--total)
only_total=1
show_all=0
shift
;;
-a|--all)
human=1
details=1
shift
;;
-x|--ext)
ext_filter="${2#.}"
shift 2
;;
-X|--ext-list)
ext_list="$2"
shift 2
;;
--min)
min_size="$2"
shift 2
;;
--max)
max_size="$2"
shift 2
;;
--)
shift
break
;;
*)
disp E "Invalid option: $1"
return 1
;;
esac esac
shift shift
done done
[ -n "$1" ] && path="$1" [[ -n "$1" ]] && path="$1"
# Prepare find filters # Prepare find filters
local find_cmd=(find "$path" -type f) local find_cmd=(find "$path" -type f)
# Extension simple # Single extension filter
if [[ -n "$ext_filter" ]]; then if [[ -n "$ext_filter" ]]; then
find_cmd+=(-iname "*.$ext_filter") find_cmd+=(-iname "*.$ext_filter")
fi fi
# Extension liste # Extension list filter
if [[ -n "$ext_list" ]]; then if [[ -n "$ext_list" ]]; then
IFS=',' read -ra exts <<< "$ext_list" IFS=',' read -ra exts <<< "$ext_list"
find_cmd+=('(') find_cmd+=('(')
@@ -262,7 +465,7 @@ file_stats()
find_cmd+=(')') find_cmd+=(')')
fi fi
# Taille min/max (à évaluer en octets) # Minimum/maximum size filters (evaluated in bytes)
if [[ -n "$min_size" ]]; then if [[ -n "$min_size" ]]; then
find_cmd+=(-size +"$(numfmt --from=iec "$min_size")"c) find_cmd+=(-size +"$(numfmt --from=iec "$min_size")"c)
fi fi
@@ -270,13 +473,28 @@ file_stats()
find_cmd+=(-size -"$(( $(numfmt --from=iec "$max_size") + 1 ))"c) find_cmd+=(-size -"$(( $(numfmt --from=iec "$max_size") + 1 ))"c)
fi fi
# Exécution # Execution
"${find_cmd[@]}" -printf "%s\n" 2>/dev/null | sort -n | awk -v human="$human" -v details="$details" -v only_avg="$only_avg" -v only_med="$only_med" -v only_count="$only_count" -v only_total="$only_total" -v show_all="$show_all" -v path="$path" ' "${find_cmd[@]}" -printf "%s\n" 2>/dev/null | sort -n | \
awk -v human="$human" -v details="$details" -v only_avg="$only_avg" \
-v only_med="$only_med" -v only_count="$only_count" \
-v only_total="$only_total" -v show_all="$show_all" -v path="$path" '
# Convert function
function human_readable(x) { function human_readable(x) {
split("B KiB MiB GiB TiB", units) split("B KiB MiB GiB TiB", units)
for (i=1; x>=1024 && i<5; i++) x /= 1024 i = 1
while (x >= 1024 && i < 5) {
x /= 1024
i++
}
return sprintf("%.2f %s", x, units[i]) return sprintf("%.2f %s", x, units[i])
} }
# Display function
function out(label, val, is_size) {
if (human == 1 && is_size == 1) val = human_readable(val)
printf "%-20s : %s\n", label, val
}
{ {
sizes[NR] = $1 sizes[NR] = $1
total += $1 total += $1
@@ -288,50 +506,56 @@ file_stats()
bucket[b]++ bucket[b]++
} }
} }
END { END {
count = NR count = NR
if (count == 0) { if (count == 0) {
print "Aucun fichier trouvé."; exit print "No files found."
exit
} }
moyenne = total / count average = total / count
if (count % 2 == 1)
mediane = sizes[(count + 1) / 2]
else
mediane = (sizes[count / 2] + sizes[count / 2 + 1]) / 2
function out(label, val) { # Median calculation: exact using sorted array values
if (human) val = human_readable(val) if (count % 2 == 1) {
printf "%-20s : %s\n", label, val median = sizes[(count + 1) / 2]
} else {
idx = count / 2
median = (sizes[idx] + sizes[idx + 1]) / 2
} }
if (only_avg) out("Taille moyenne", moyenne) if (only_avg) out("Average size", average, 1)
else if (only_med) out("Taille médiane", mediane) else if (only_med) out("Median size", median, 1)
else if (only_count) printf "Nombre de fichiers : %d\n", count else if (only_count) out("Number of files", count, 0)
else if (only_total) out("Taille totale", total) else if (only_total) out("Total size", total, 1)
else { else {
if (show_all || human || details) { if (show_all || human || details) {
printf "Statistiques sur \"%s\"\n", path printf "Statistics for \"%s\"\n", path
printf "-------------------------\n" printf "-------------------------\n"
} }
out("Nombre de fichiers", count) out("Number of files", count, 0)
out("Taille totale", total) out("Total size", total, 1)
out("Taille moyenne", moyenne) out("Average size", average, 1)
out("Taille médiane", mediane) out("Median size", median, 1)
out("Taille minimale", min) out("Minimum size", min, 1)
out("Taille maximale", max) out("Maximum size", max, 1)
} }
if (details) { if (details) {
print "\nHistogramme des tailles :" print "\nSize histogram:"
for (i = 0; i in bucket; i++) {
low = 2^i # Use a separate array for the loop to avoid collision
high = 2^(i+1) for (b in bucket) {
if (i == 0) # Pre-calculate label parts
label = sprintf("%4s %4s", "0", "1K") # 1024^0 = 1 (B), 1024^1 = 1K, etc.
else low = (b == 0) ? 0 : (1024^b)
label = sprintf("%4s %4s", human_readable(low), human_readable(high)) high = 1024^(b+1)
printf "%-20s : %5d fichiers\n", label, bucket[i]
label = sprintf("%-9s %-9s",
(b == 0) ? "0" : human_readable(low),
human_readable(high))
# We store buckets in an array, access them by index b
printf "%-25s : %6d fichiers\n", label, bucket[b]
} }
} }
}' }'
@@ -341,4 +565,231 @@ export -f file_stats
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Find the biggest files in a directory tree
# Usage: findbig [options] [directory]
# Options:
# -h : display help screen
# -d : display details (ls -l) for each file
# -x : do not cross filesystem boundaries
# -l : limit : number of files to return (default is 10)
findbig()
{
local details=0 limit=10 one_fs=0
local PARSED
PARSED=$(getopt -o hdl:x --long help,details,limit:,one-fs -n 'findbig' -- "$@")
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"findbig --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "findbig: Find the N biggest files in a directory tree.\n\n"
printf "Usage: findbig [options] [directory]\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-d, --details\t\tShow detailed file info (ls -ld)\n"
printf "\t-l, --limit N\t\tNumber of files to return (default: 10)\n"
printf "\t-x, --one-fs\t\tDo not cross filesystem boundaries\n"
return 0
;;
-d|--details)
details=1
shift
;;
-l|--limit)
limit="$2"
[[ "$limit" =~ ^[0-9]+$ ]] || {
disp E "Invalid limit: must be a positive integer."
return 1
}
shift 2
;;
-x|--one-fs)
one_fs=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid option: $1"
return 1
;;
esac
done
local dir="${1:-.}"
# Prepare find arguments in an array for cleaner handling
local find_args=(-L "$dir")
(( one_fs )) && find_args+=(-xdev)
find_args+=(-type f)
# Logic: find files, print size and path, sort numeric reverse, take N
if (( details )); then
find "${find_args[@]}" -printf "%s %p\n" 2>/dev/null | sort -rn | head -n "$limit" |
while IFS= read -r line; do
local path="${line#* }"
ls -ld -- "$path"
done
else
find "${find_args[@]}" -printf "%s %p\n" 2>/dev/null | sort -rn | head -n "$limit"
fi
}
export -f findbig
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Find empty files in a directory tree
# Usage: findzero [options] [directory]
# Options:
# -h : display help screen
# -d : display details (ls -l) for each file
# -x : do not cross filesystem boundaries
# --delete : delete empty files and display their paths
findzero()
{
local delete=0 details=0 one_fs=0
local PARSED
# o: options, long: long equivalents
PARSED=$(getopt -o hdx --long help,details,one-fs,delete -n 'findzero' -- "$@")
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"findzero --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "findzero: Find or delete empty files in a directory tree.\n\n"
printf "Usage: findzero [options] [directory]\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-d, --details\t\tShow detailed file info (ls -ls)\n"
printf "\t-x, --one-fs\t\tDo not cross filesystem boundaries\n"
printf "\t--delete\t\tActually remove the empty files\n"
return 0 ;;
-d|--details)
details=1
shift
;;
-x|--one-fs)
one_fs=1
shift
;;
--delete)
delete=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid option: $1"
return 1
;;
esac
done
local dir="${1:-.}"
local find_args=("-L" "$dir" "-type" "f" "-empty")
(( one_fs )) && find_args+=("-xdev")
# Execution logic
if (( delete )); then
disp W "Deleting empty files in $dir..."
find "${find_args[@]}" -delete -print
elif (( details )); then
find "${find_args[@]}" -ls
else
find "${find_args[@]}"
fi
}
export -f findzero
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Find dead symbolic links in a directory tree
# Usage: finddead [options] [directory]
# Options:
# -h : display help screen
# -d : display details (ls -l) for each link
# -x : do not cross filesystem boundaries
# --delete : delete dead links and display their paths
finddead()
{
local delete=0 details=0 one_fs=0
local PARSED
PARSED=$(getopt -o hdx --long help,details,one-fs,delete -n 'finddead' -- "$@")
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"finddead --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "finddead: Find or delete dead/broken symbolic links.\n\n"
printf "Usage: finddead [options] [directory]\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-d, --details\t\tShow detailed symlink info (ls -ls)\n"
printf "\t-x, --one-fs\t\tDo not cross filesystem boundaries\n"
printf "\t--delete\t\tActually remove the dead links\n"
return 0 ;;
-d|--details)
details=1
shift
;;
-x|--one-fs)
one_fs=1
shift
;;
--delete)
delete=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid option: $1"
return 1
;;
esac
done
local dir="${1:-.}"
# -xtype l searches for links that do not point to an existing file
local find_args=("$dir" "-xtype" "l")
(( one_fs )) && find_args+=("-xdev")
# Execution logic
if (( delete )); then
disp W "Deleting dead symlinks in $dir..."
find "${find_args[@]}" -delete -print
elif (( details )); then
find "${find_args[@]}" -ls
else
find "${find_args[@]}"
fi
}
export -f finddead
# ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -36,35 +36,77 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Make non-IT peoples think you're busy doing something hard # Make non-IT peoples think you're busy doing something hard
# ------------------------------------------------------------------------------ # Usage: busy [options] [pattern]
# Options:
# --delay=<ms> : add a delay between each line output (milliseconds)
# pattern : the string to search for in the hexdump output (default is "ca fe")
busy() busy()
{ {
local pattern="ca fe" local pattern="ca fe" delay_ms=0
for arg in "$@"; do
case "$arg" in local PARSED
--delay=*) # Short: h, p:, d:
delay_ms="${arg#*=}" # Long: help, pattern:, delay:
if ! [[ $delay_ms =~ ^[0-9]+$ ]]; then PARSED=$(getopt -o hp:d: --long help,pattern:,delay: -n 'busy' -- "$@")
disp E "Invalid delay value, must be an integer (milliseconds)." if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"busy --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "busy: Monitor /dev/urandom for a specific pattern.\n\n"
printf "Usage: busy [options] [pattern]\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-p, --pattern PATTERN\tHex pattern to search (default: \"ca fe\")\n"
printf "\t-d, --delay MS\t\tDelay between matches in milliseconds\n"
return 0
;;
-p|--pattern)
pattern="$2"
shift 2
;;
-d|--delay)
delay_ms="$2"
if ! [[ "$delay_ms" =~ ^[0-9]+$ ]]; then
disp E "Invalid delay: must be an integer (milliseconds)."
return 1 return 1
fi fi
shift 2
;;
--)
shift
break
;; ;;
*) *)
pattern="$arg" disp E "Invalid option: $1"
return 1
;; ;;
esac esac
done done
# Convert milliseconds to seconds for 'sleep' # Convert milliseconds to seconds for 'sleep'
local delay_s=$(awk "BEGIN { printf \"%.3f\", $delay_ms / 1000 }") local delay_s=$(awk "BEGIN{
printf \"%.3f\", $delay_ms / 1000 }")
cat /dev/urandom | hexdump -C | grep --line-buffered "$pattern" | \ # Monitor /dev/urandom
while read -r line; do (
echo $line hexdump -C < /dev/urandom | grep -iF --line-buffered "$pattern" | \
[[ $delay_ms -gt 0 ]] && sleep "$delay_s" while read -r line; do
done echo "$line"
unset pattern [[ $delay_ms -gt 0 ]] && sleep "$delay_s"
done
) & local sub_pid=$!
IFS= read -r -n 1 -s _ </dev/tty
kill -- -"$sub_pid" 2>/dev/null || kill "$sub_pid" 2>/dev/null
wait "$sub_pid" 2>/dev/null
return 0
} }
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -36,38 +36,53 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Display list of commands and general informations # Display list of commands and general informations
# ------------------------------------------------------------------------------ # Usage: help
help() help()
{ {
cat <<EOF printf "${BIWhite}Welcome to your profile! Here is a list of available commands:${DEFAULTCOL}\n\n"
clean Erase backup files printf "check_updates\tCheck for new versions of profile\n"
dpkgs Search for the given package in the installed ones printf "clean\t\tErase backup files\n"
gpid Give the list of PIDs for the given process name printf "disp\t\tDisplay formatted info/warning/error/debug messages\n"
isipv4 Tell if the given IPv4 is valid printf "dwl\t\tDownload a URL to a local file\n"
isipv6 Tell if the given IPv6 is valid printf "expandlist\tExpand and quote item lists\n"
ku Kill process owned by users in parameter printf "file_stats\tDisplay file size statistics for a path\n"
mcd Create a directory and go inside printf "findbig\t\tFind biggest files in the given (or current) directory\n"
meteo Display curent weather forecast for the configured city printf "finddead\tFind dead symbolic links in the given (or current) directory\n"
ppg Display process matching the given parameter printf "findzero\tFind empty files in the given (or current) directory\n"
ppn Display process matching the exact process name given in parameter printf "genpwd\t\tGenerate secure passwords\n"
ppu Display processes owned by the given user printf "gpid\t\tGive the list of PIDs for the given process name\n"
rain Let the rain fall printf "isipv4\t\tTell if the given IPv4 is valid\n"
rmhost Remove host (IP and/or DNS name) for current known_host printf "isipv6\t\tTell if the given IPv6 is valid\n"
rmspc Remove spaces from all the files in working directory printf "ku\t\tKill process owned by users in parameter\n"
setc Set console language to C printf "matrix\t\tDisplay matrix-style digital rain\n"
setfr Set console language to French printf "mcd\t\tCreate a directory and go inside\n"
settrace Activate/deactivate call trace for script debugging printf "meteo\t\tDisplay current weather forecast for the configured city\n"
setus Set console language to US English printf "myextip\tDisplay current external/public IP\n"
showinfo Show the welcoming baner with basic system information printf "pkgs\t\tSearch for the given package in installed ones\n"
ssr Do a root login to the given address printf "ppg\t\tDisplay process matching the given parameter\n"
taz Compress smartly the given files or directory printf "ppn\t\tDisplay process matching the exact process name given in parameter\n"
utaz Uncompress all zip files in the given (or current) directory printf "ppu\t\tDisplay processes owned by the given user\n"
ver Display version of your copy of profile printf "profile_upgrade\tUpgrade profile to the latest version\n"
printf "pwdscore\tCalculate password strength score\n"
printf "rain\t\tLet the rain fall\n"
printf "rmhost\t\tRemove host (IP and/or DNS name) from current known_hosts\n"
printf "rmspc\t\tRemove spaces from file and directory names\n"
printf "setlocale\tSet console language to the current locale\n"
printf " * setc\tSet console language to C\n"
printf " * setfr\tSet console language to French\n"
printf " * setus\tSet console language to US English\n"
printf "settrace\tActivate/deactivate call trace for script debugging\n"
printf "showinfo\tShow welcome banner with basic system information\n"
printf "ssr\t\tDo a root login to the given address\n"
printf "taz\t\tCompress smartly the given files or directory\n"
printf "urlencode\tURL-encode the given text\n"
printf "utaz\t\tUncompress archives in the given (or current) directory\n"
printf "ver\t\tDisplay version of your copy of profile\n\n"
Please use <command> --help to obtain usage details. printf "\nPlease use <command> --help to obtain usage details.\n"
EOF
} }
export -f help export -f help
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -36,51 +36,136 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Show profile version # Show profile version
# ------------------------------------------------------------------------------ # Usage: ver
ver() ver()
{ {
local PARSED
PARSED=$(getopt -o h --long help -n 'ver' -- "$@")
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"ver --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "ver: Display the current profile version.\nUsage: ver\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"ver --help\" to display usage."
return 1
;;
esac
done
[[ -z $PROFVERSION ]] && \ [[ -z $PROFVERSION ]] && \
disp W "No version defined. Profile is probably badly installed." && \ disp W "No version defined. Profile is probably badly installed." && \
return 1 return 1
disp "Profile version $PROFVERSION." disp "Profile version $PROFVERSION."
} }
export -f ver export -f ver
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Display weather of the given city (or default one) # Display weather for the given city (or the default one)
# ------------------------------------------------------------------------------ # Usage: meteo [city1 city2 ...]
meteo() meteo()
{ {
local encoded cities=("$@") local PARSED
PARSED=$(getopt -o h --long help -n 'meteo' -- "$@")
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"meteo --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "meteo: Fetch weather data.\n"
printf "Usage: meteo [city1 city2 ...]\n"
printf "If no city is provided, the default city from configuration will be used.\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"meteo --help\" to display usage."
return 1
;;
esac
done
local cities=("$@")
local city="" encoded=""
[[ $# -eq 0 ]] && cities=("$DEFAULT_CITY") [[ $# -eq 0 ]] && cities=("$DEFAULT_CITY")
for city in "${cities[@]}"; do for city in "${cities[@]}"; do
encoded=$(urlencode "$city") encoded=$(urlencode "$city")
curl -s "https://wttr.in/$encoded" || \ dwl "https://wttr.in/$encoded" || \
disp E "Failed fetching datas for $city." disp E "Failed to fetch weather data for $city."
done done
} }
export -f meteo export -f meteo
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Display system general information # Display system general information
# ------------------------------------------------------------------------------ # Usage: showinfo
showinfo() showinfo()
{ {
echo -e "\n" local PARSED
if command -v figlet >/dev/null 2>&1; then
if [[ -s /usr/share/figlet/ansi_shadow.flf ]]; then PARSED=$(getopt -o h --long help -n 'showinfo' -- "$@")
local figopt="-f ansi_shadow" if [[ $? -ne 0 ]]; then
fi disp E "Invalid options, use \"showinfo --help\" to display usage."
if [[ -n $figopt ]]; then return 1
figlet -k $figopt $(hostname)
else
figlet $(hostname)
fi
else
hostname -f
fi fi
echo "" eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "showinfo: Display system information (hostname, kernel, uptime and fetch output when available).\n"
printf "Usage: showinfo\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"showinfo --help\" to display usage."
return 1
;;
esac
done
local hostname_str
local figopt=()
hostname_str="$(hostname)"
printf "\n"
if command -v figlet >/dev/null 2>&1; then
[[ -s /usr/share/figlet/ansi_shadow.flf ]] && \
figopt=(-f ansi_shadow)
figlet -k "${figopt[@]}" "$hostname_str"
else
printf "%s\n" "$hostname_str"
fi
printf "\n"
if command -v neofetch >/dev/null 2>&1; then if command -v neofetch >/dev/null 2>&1; then
neofetch neofetch
elif command -v fastfetch >/dev/null 2>&1; then elif command -v fastfetch >/dev/null 2>&1; then
@@ -90,15 +175,17 @@ showinfo()
if [[ -s /etc/os-release ]]; then if [[ -s /etc/os-release ]]; then
# shellcheck disable=SC1091 # shellcheck disable=SC1091
. /etc/os-release . /etc/os-release
echo "$NAME $VERSION" printf "%s %s\n" "$NAME" "$VERSION"
else else
cat /proc/version cat /proc/version
fi fi
echo "Uptime: $(uptime -p)" printf "Uptime: %s\n" "$(uptime -p)"
) )
fi fi
} }
export -f showinfo export -f showinfo
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
load_conf info
# EOF # EOF

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -34,7 +34,8 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
locale_check() { locale_check()
{
locale -a | grep -qx "$1" || { locale -a | grep -qx "$1" || {
disp W "Locale '$1' is not installed on this system." disp W "Locale '$1' is not installed on this system."
return 1 return 1
@@ -45,9 +46,36 @@ locale_check() {
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Change locale to the given one in parameter # Change locale to the given one in parameter
# ------------------------------------------------------------------------------ # Usage: setlocale <locale>
setlocale() setlocale()
{ {
local PARSED
PARSED=$(getopt -o h --long help -n 'setlocale' -- "$@")
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"setlocale --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "setlocale: Configure system environment locale variables.\n\n"
printf "Usage: setlocale <locale>\n\n"
printf "Options:\n"
printf " -h, --help Display this help screen\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"setlocale --help\" to display usage."
return 1
;;
esac
done
local loc=$1 local loc=$1
[[ -z $loc ]] && disp E "No locale specified." && return 1 [[ -z $loc ]] && disp E "No locale specified." && return 1
@@ -64,11 +92,12 @@ setlocale()
disp I "Locale set to $loc." disp I "Locale set to $loc."
} }
export -f setlocale export -f setlocale
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Special case : change locale to C standard # Special case : change locale to C standard
# ------------------------------------------------------------------------------ # Usage: setc
setc() setc()
{ {
# Locale definitions # Locale definitions
@@ -76,27 +105,77 @@ setc()
disp I "Locale changed to standard C (POSIX)." disp I "Locale changed to standard C (POSIX)."
} }
export -f setc export -f setc
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Change locale to French # Build dynamic locale shortcuts from SET_LOCALE
# ------------------------------------------------------------------------------ # Expected format:
setfr() # SET_LOCALE="fr:fr_FR.UTF-8,us:en_US.UTF-8,es:es_ES.UTF-8"
# This creates functions:
# setfr, setus, setes, ...
build_locale_shortcuts()
{ {
# Set fr locale definitions local cfg="${SET_LOCALE:-}"
setlocale "fr_FR.UTF-8" local item="" alias="" loc="" fname=""
local -a locale_items=()
[[ -z "$cfg" ]] && return 0
IFS=',' read -r -a locale_items <<< "$cfg"
for item in "${locale_items[@]}"; do
# Trim surrounding spaces
item="${item#"${item%%[![:space:]]*}"}"
item="${item%"${item##*[![:space:]]}"}"
[[ -z "$item" ]] && continue
if [[ "$item" != *:* ]]; then
disp W "Ignoring invalid SET_LOCALE entry: '$item' (expected alias:locale)."
continue
fi
alias="${item%%:*}"
loc="${item#*:}"
# Trim alias/locale spaces
alias="${alias#"${alias%%[![:space:]]*}"}"
alias="${alias%"${alias##*[![:space:]]}"}"
loc="${loc#"${loc%%[![:space:]]*}"}"
loc="${loc%"${loc##*[![:space:]]}"}"
# Validate alias for safe function names
if [[ ! "$alias" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
disp W "Ignoring unsafe locale alias '$alias' in SET_LOCALE."
continue
fi
[[ -z "$loc" ]] && {
disp W "Ignoring empty locale for alias '$alias' in SET_LOCALE."
continue
}
fname="set${alias}"
# Optional collision warning
if declare -F "$fname" >/dev/null 2>&1; then
disp W "Overriding existing function '$fname'."
fi
# Build function dynamically
# shellcheck disable=SC2016
eval "${fname}() { setlocale \"$loc\"; }"
# shellcheck disable=SC2163
export -f "$fname"
done
unset cfg item alias loc fname locale_items
} }
export -f setfr export -f build_locale_shortcuts
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Change locale to US (needed by Steam)
# ------------------------------------------------------------------------------
setus()
{
setlocale "en_US.UTF-8"
}
export -f setus
load_conf lang
build_locale_shortcuts
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -35,38 +35,97 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Determine if parameter is a valid IPv4 address # Download a resource using curl, wget, or fetch.
# Usage: dwl <url> [output_file]
dwl()
{
case "$1" in
--help|-h)
echo "Usage: dwl <url> [output_file]"
echo "Downloads a resource using curl, wget, or fetch."
echo ""
echo "Arguments:"
echo " url The full URL to download (http/https/ftp)."
echo " output_file (Optional) Path to save the file. If omitted, prints to stdout."
return 0
;;
"")
echo "Error: URL argument is missing." >&2
echo "Try 'get_resource --help' for usage." >&2
return 1
;;
esac
case "$1" in
http://*|https://*|ftp://*) ;;
*)
echo "Error: '$1' does not look like a valid URL. Must start with http://, https://, or ftp://" >&2
return 1
;;
esac
local url="$1"
local output="$2"
if command -v curl >/dev/null 2>&1; then
[ -z "$output" ] && curl -sL "$url" || curl -sL -o "$output" "$url"
elif command -v wget >/dev/null 2>&1; then
[ -z "$output" ] && wget -qO- "$url" || wget -q -O "$output" "$url"
elif command -v fetch >/dev/null 2>&1; then
[ -z "$output" ] && fetch -o - "$url" || fetch -o "$output" "$url"
else
echo "Error: No download utility (curl, wget, or fetch) found." >&2
return 1
fi
}
export -f dwl
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Determine if parameter is a valid IPv4 address
# Usage: isipv4 <ip_address>
isipv4() isipv4()
{ {
# Set up local variables # Set up local variables
local ip=$1 local ip=$1
[[ -z $ip ]] && return 1 [[ -z $ip ]] && return 1
# Start with a regex format test # Start with a regex format test (four octets)
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then if [[ $ip =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
local old_ifs=$IFS local old_ifs=$IFS
IFS="." IFS='.'
ip=($ip) read -r -a ip_arr <<< "$ip"
IFS=$old_ifs IFS=$old_ifs
if [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 &&
${ip[2]} -le 255 && ${ip[3]} -le 255 ]]; then # Ensure each octet is between 0 and 255
if [[ -t 1 ]]; then local oct
disp "The given IPv4 is valid." for oct in "${ip_arr[@]}"; do
# Reject leading plus/minus or empty entries
if [[ -z $oct || $oct =~ [^0-9] ]]; then
[[ -t 1 ]] && disp "The given parameter is NOT a valid IPv4."
return 1
fi fi
return 0 if (( oct > 255 )); then
fi [[ -t 1 ]] && disp "The given parameter is NOT a valid IPv4."
fi return 1
if [[ -t 1 ]]; then fi
disp "The given parameter is NOT a valid IPv4." done
[[ -t 1 ]] && disp "The given IPv4 is valid."
return 0
fi fi
[[ -t 1 ]] && disp "The given parameter is NOT a valid IPv4."
return 1 return 1
} }
export -f isipv4 export -f isipv4
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Determine if parameter is a valid IPv4 address # Determine if parameter is a valid IPv6 address
# ------------------------------------------------------------------------------ # Usage: isipv6 <ip_address>
isipv6() isipv6()
{ {
local ip="$1" local ip="$1"
@@ -83,10 +142,12 @@ isipv6()
return 1 return 1
} }
export -f isipv6 export -f isipv6
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Encode a string so it can be used as a URL parameter # Encode a string so it can be used as a URL parameter
# ------------------------------------------------------------------------------ # Usage: urlencode <string>
urlencode() { urlencode() {
local LANG=C local LANG=C
local str="$*" local str="$*"
@@ -94,13 +155,117 @@ urlencode() {
for (( i = 0; i < length; i++ )); do for (( i = 0; i < length; i++ )); do
local c="${str:i:1}" local c="${str:i:1}"
case "$c" in case "$c" in
[a-zA-Z0-9.~_-]) printf "$c" ;; [a-zA-Z0-9.~_-]) printf '%s' "$c" ;;
' ') printf '+' ;; ' ') printf '+' ;;
*) printf '%%%02X' "'$c" #| cut -d' ' -f2 ;; *) printf '%%%02X' "'$c" #| cut -d' ' -f2 ;;
esac esac
done done
} }
export -f urlencode export -f urlencode
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Fetch and display external IP information
# Usage: myextip [-i|--ip] [-s|--isp] [-l|--loc] [-c|--coord]
# If no option is provided, all information will be displayed.
# Options:
# -h, --help Display help screen
# -i, --ip Display only the external IP address
# -s, --isp Display only the ISP name
# -l, --loc Display only the location (city, region, country)
# -c, --coord Display only the coordinates (latitude, longitude)
# -a, --as Display only the Autonomous System (AS) information
# -R, --raw Display raw JSON response
myextip() {
local show_ip=false show_isp=false show_loc=false
local show_coord=false show_as=false show_raw=false
local all=true
# Parse arguments
while [[ "$#" -gt 0 ]]; do
case "$1" in
-i|--ip)
show_ip=true
all=false
;;
-s|--isp)
show_isp=true
all=false
;;
-l|--loc)
show_loc=true
all=false
;;
-c|--coord)
show_coord=true
all=false
;;
-a|--as)
show_as=true
all=false
;;
-R|--raw)
all=false
show_raw=true
;;
-h|--help)
printf "Fetch and display external IP information.\n\n"
printf "Usage: myextip [-i|--ip] [-s|--isp] [-l|--loc] [-c|--coord] [-a|--as] [-R|--raw]\n\n"
printf "Options:\n"
printf "\t-h, --help\tDisplay this help screen\n"
printf "\t-i, --ip\tDisplay only the external IP address\n"
printf "\t-s, --isp\tDisplay only the ISP name\n"
printf "\t-l, --loc\tDisplay only the location (city, region, country)\n"
printf "\t-c, --coord\tDisplay only the coordinates (latitude, longitude)\n"
printf "\t-a, --as\tDisplay only the Autonomous System (AS) information\n"
printf "\t-R, --raw\tDisplay raw JSON response\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Unknown option: $1, use \"myextip --help\" to display usage."
return 1
;;
esac
shift
done
# Fetch data. Allow overriding endpoint via env var MYEXTIP_URL
local MYEXTIP_URL
MYEXTIP_URL=${MYEXTIP_URL:-http://ip-api.com/json/}
local response
if ! response=$(dwl "$MYEXTIP_URL"); then
disp E "Failed to fetch external IP information from $MYEXTIP_URL"
return 2
fi
# Parse with jq when available and when raw wasn't requested. The jq filter
# is tolerant to field-name differences between providers (ip-api / ipinfo).
if command -v jq >/dev/null 2>&1 && [[ "$show_raw" != true ]]; then
echo "$response" | jq -r --argjson all "$all" --argjson ip "$show_ip" \
--argjson isp "$show_isp" --argjson loc "$show_loc" \
--argjson coord "$show_coord" --argjson as "$show_as" '
[
(if $all or $ip then "IP Address : \(.query // .ip)" else empty end),
(if $all or $isp then "ISP : \(.isp // .org)" else empty end),
(if $all or $loc then
("Location : " + ((.city // "") + (if .city then ", " else "" end) + (if .regionName then .regionName else .region end) + (if .country then ", " + .country else "" end)))
else empty end),
(if $all or $coord then (if (.lat and .lon) then "Coordinates: \(.lat), \(.lon)" elif .loc then "Coordinates: \(.loc)" else empty end) else empty end),
(if $all or $as then "AS : \(.as // .org)" else empty end)
] | .[]'
else
[[ "$show_raw" != true ]] && disp W "jq is not installed, displaying raw JSON response."
echo "$response"
fi
}
export -f myextip
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -36,38 +36,53 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Look for a package within installed one # Look for a package within installed one
# ------------------------------------------------------------------------------ # Usage: dpkgs <string>
pkgs() pkgs()
{ {
local count=0 local ignore_case=0
for opt in $@; do
case $opt in
"-h" | "--help")
echo "dpkgs: look for an installed package by it's name."
echo
echo "Usage: dpkgs <string>"
return 0
;;
"-"*) local PARSED
disp E "Invalid option, use \"dpkgs --help\" to display usage." PARSED=$(getopt -o hi --long help,ignore-case -n 'pkgs' -- "$@")
echo if [[ $? -ne 0 ]]; then
return 1 disp E "Invalid options, use \"pkgs --help\" to display usage."
;; return 1
fi
eval set -- "$PARSED"
*) while true; do
local pkg=$1 && shift case "$1" in
count=$(($count + 1)) -h|--help)
[[ $count -gt 1 ]] && printf "pkgs: Look for an installed package by its name.\n\n"
disp E "Please specify a package name, without space, eventually partial." && printf "Usage: pkgs [options] <string>\n\n"
printf "Options:\n"
printf "\t-h, --help\tDisplay this help screen\n"
printf "\t-i, --ignore-case\tIgnore case distinctions\n"
return 0
;;
-i|--ignore-case)
ignore_case=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid option: $1"
return 1 return 1
;;
;;
esac esac
done done
[[ $count -lt 1 ]] &&
disp E "Please specify a package name, without space, eventually partial." && local pkg="$1"
[[ -z "$pkg" ]] && {
disp E "Please specify a package name, without space, eventually partial."
return 1 return 1
}
# Build grep command
local grep_opt=""
(( ignore_case )) && grep_opt="-i"
command -v dpkg >/dev/null 2>&1 && local cmd="dpkg -l" command -v dpkg >/dev/null 2>&1 && local cmd="dpkg -l"
command -v rpm >/dev/null 2>&1 && local cmd="rpm -qa" command -v rpm >/dev/null 2>&1 && local cmd="rpm -qa"
@@ -75,9 +90,10 @@ pkgs()
disp E "No usable package manager seems unavialable." disp E "No usable package manager seems unavialable."
return 2 return 2
fi fi
$cmd | grep $pkg $cmd | grep $grep_opt $pkg
} }
export -f pkgs export -f pkgs
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -36,18 +36,38 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Search processes matching the given string # Search processes matching the given string
# ------------------------------------------------------------------------------ # Usage: ppg <string>
ppg() ppg()
{ {
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
printf "ppg: Search processes matching the given string.\n\n"
printf "Usage: ppg <string>\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
return 0
fi
if [[ -z "$1" ]]; then
disp E "Usage: ppg <string>"
return 1
fi
ps -edf | grep "$@" | grep -v "grep $@" ps -edf | grep "$@" | grep -v "grep $@"
} }
export -f ppg export -f ppg
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# List processes owned by a specific user # List processes owned by a specific user
# ------------------------------------------------------------------------------ # Usage: ppu <username>
ppu() ppu()
{ {
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
printf "ppu: List processes owned by a specific user.\n\n"
printf "Usage: ppu <username>\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
return 0
fi
if [[ -z "$1" ]]; then if [[ -z "$1" ]]; then
disp E "Usage: ppu <username>" disp E "Usage: ppu <username>"
return 1 return 1
@@ -58,12 +78,21 @@ ppu()
ps -u "$1" -o pid,user,%cpu,%mem,start,time,command ps -u "$1" -o pid,user,%cpu,%mem,start,time,command
} }
export -f ppu export -f ppu
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# List processes by exact command name (no path/parameters) # List processes by exact command name (no path/parameters)
# ------------------------------------------------------------------------------ # Usage: ppn <command_name>
ppn() ppn()
{ {
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
printf "ppn: List processes by exact command name (no path/parameters).\n\n"
printf "Usage: ppn <command_name>\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
return 0
fi
if [[ -z "$1" ]]; then if [[ -z "$1" ]]; then
disp E "Usage: ppn <command_name>" disp E "Usage: ppn <command_name>"
return 1 return 1
@@ -75,12 +104,25 @@ ppn()
ps -eo pid,comm | grep -w "$1" ps -eo pid,comm | grep -w "$1"
} }
export -f ppn export -f ppn
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Get PID list of the given process name # Get PID list of the given process name
# ------------------------------------------------------------------------------ # Usage: ppid <process_name [process_name2 ...]>
gpid() gpid()
{ {
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
printf "gpid: Get PID list of the given process name.\n\n"
printf "Usage: gpid <process_name [process_name2 ...]>\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
return 0
fi
if [[ -z "$1" ]]; then
disp E "Usage: gpid <process_name [process_name2 ...]>"
return 1
fi
[[ $UID -eq 0 ]] && local psopt="-A" [[ $UID -eq 0 ]] && local psopt="-A"
[[ $# -eq 1 ]] && local single=1 [[ $# -eq 1 ]] && local single=1
for pid in $@; do for pid in $@; do
@@ -94,34 +136,65 @@ gpid()
[[ $result ]] || return 1 [[ $result ]] || return 1
} }
export -f gpid export -f gpid
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Kill all processes owned by the given users (kill user) # Kill all processes owned by the given users (kill user)
# ------------------------------------------------------------------------------ # Usage: ku <username1 [username2 ...]>
ku() ku()
{ {
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
printf "ku: Kill all processes owned by the given users.\n\n"
printf "Usage: ku <username1 [username2 ...]>\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
return 0
fi
if [[ -z "$1" ]]; then
disp E "Usage: ku <username1 [username2 ...]>"
return 1
fi
for u in $@; do for u in $@; do
killall -u "$u" killall -u "$u"
done done
} }
export -f ku export -f ku
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Kill all children of a process then the process (kill tree) # Kill all children of a process then the process (kill tree)
# ------------------------------------------------------------------------------ # Usage: kt <pid> [kill_options]
kt() kt()
{ {
[[ -z $1 ]] && echo -e "Usage:\n\tkt <pid> [kill_options]" if [[ "$1" == "-h" || "$1" == "--help" ]]; then
printf "kt: Kill all children of a process then the process (kill tree).\n\n"
printf "Usage: kt <pid> [kill_options]\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
return 0
fi
if [[ -z "$1" ]]; then
disp E "Usage: ppg <string>"
return 1
fi
local parent_pid="$1" local parent_pid="$1"
shift shift
if [[ "$parent_pid" == "0" || "$parent_pid" == "1" ]]; then
disp E "Safety abort: Refusing to kill PID $parent_pid (system critical)."
return 1
fi
children_pids=$(pgrep -P "$parent_pid") children_pids=$(pgrep -P "$parent_pid")
for pid in $children_pids; do for pid in $children_pids; do
kt "$pid" "$@" kt "$pid" "$@" || break
done done
kill "$@" "$parent_pid" kill "$@" "$parent_pid"
} }
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -35,17 +36,26 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# timer_* functions : internal timing function for prompt # timer_* functions : internal timing function for prompt
# ------------------------------------------------------------------------------ # Usage: timer_now
# This function returns the current time in nanoseconds since the epoch. It
# first tries to use the %N format specifier for nanoseconds, but if that is
# not supported (e.g., on older systems), it falls back to seconds.
function timer_now function timer_now
{ {
date +%s%N 2>/dev/null || date +%s date +%s%N 2>/dev/null || date +%s
} }
# Usage: timer_start
# This function initializes the timer_start variable with the current time in
# nanoseconds. It is used to measure the elapsed time for the prompt.
function timer_start function timer_start
{ {
timer_start=${timer_start:-$(timer_now)} timer_start=${timer_start:-$(timer_now)}
} }
# Usage: timer_stop
# This function calculates the elapsed time since timer_start and formats it
# into a human-readable string with appropriate units (us, ms, s, m, h
function timer_stop function timer_stop
{ {
local delta_us=$((($(timer_now) - $timer_start) / 1000)) local delta_us=$((($(timer_now) - $timer_start) / 1000))
@@ -74,8 +84,11 @@ function timer_stop
} }
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Function triguered internaly by bash : defining prompt # Function triggered internally by bash : defining prompt
# ------------------------------------------------------------------------------ # Usage: set_prompt
# This function is called by bash before displaying the prompt. It sets the
# PS1 variable to a custom prompt that includes the exit status of the last
# command, the elapsed time of the last command, and the current user and host.
set_prompt() set_prompt()
{ {
local Last_Command=$? # Must come first! local Last_Command=$? # Must come first!
@@ -123,6 +136,7 @@ set_prompt()
# the text color to the default. # the text color to the default.
PS1+="$ICyan\\w \\\$$Default " PS1+="$ICyan\\w \\\$$Default "
} }
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -36,155 +36,442 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# genpwd : generate a password with different criteria # genpwd : generate a password with different criteria
# default 16 car with up and low car, symbol and number # Usage: genpwd [options] [--extracars=<cars>] [--length=<n>] [nb_passwd]
# Options:
# -h, --help Display that help screen
# -s, --nosymbols Exclude symbols
# -n, --nonumbers Exclude numbers
# -u, --noup Exclude uppercase letters
# -l, --nolow Exclude lowercase letters
# -e=<c>, --extracars=<c>
# Add the given caracters to the possible caracter list
# -L=<n>, --length=<n>
# Set length of the password (default is 16)
# -o=<n>, --occurences=<n>
# Set the maximum occurences of a same caracter (default is 2)
# The function is very slow on Windows # The function is very slow on Windows
# ------------------------------------------------------------------------------
genpwd() genpwd()
{ {
local length=16 local length=16
local occurs=2 # Bug, if set to 1, seems to be ignored local occurs=2
local symb=1 maj=1 min=1 numb=1 local symb=1 maj=1 min=1 numb=1
local nbpwd=1 local nbpwd=1
local extcar local extcar=""
for opt in $@; do local PARSED
case $opt in PARSED=$(getopt -o hsnule:L:o: --long \
"-h" | "--help") help,nosymbols,nonumbers,noup,nolow,extracars:,length:,occurences:,occurrences: \
echo "genpwd: generate one or more secure random password." -n 'genpwd' -- "$@")
echo if [[ $? -ne 0 ]]; then return 1; fi
echo "Usage: genpwd [options] [--extracars=<cars>] [--length=<n>] [nb_passwd]" eval set -- "$PARSED"
echo
echo "Options:" while true; do
echo " -h, --help Display that help screen" case "$1" in
echo " -s, --nosymbols Exclude symbols" -h|--help)
echo " -n, --nonumbers Exclude numbers" printf "genpwd: Generate random password(s).\n\n"
echo " -u, --noup Exclude uppercase letters" printf "Usage: genpwd [options] [nb_passwd]\n\n"
echo " -l, --nolow Exclude lowercase letters" printf "Options:\n"
echo " -e=<c>, --extracars=<c>" printf "\t-h, --help\t\tDisplay this help screen\n"
echo " Add the given caracters to the possible caracter list" printf "\t-s, --nosymbols\t\tExclude symbols\n"
echo " -L=<n>, --length=<n>" printf "\t-n, --nonumbers\t\tExclude numbers\n"
echo " Set length of the password (default is $length)" printf "\t-u, --noup\t\tExclude uppercase letters\n"
echo " -o=<n>, --occurences=<n>" printf "\t-l, --nolow\t\tExclude lowercase letters\n"
echo " Set the maximum occurences of a same caracter (default is $occurs)" printf "\t-e, --extracars <c>\tAdd characters to the pool\n"
echo printf "\t-L, --length <n>\tSet password length (default: 16)\n"
echo "If the --extracars parameter is given, at least one of the given caracter will" printf "\t-o, --occurences <n>\tMax occurrences per character (default: 2)\n"
echo "be used in the final password." return 0
echo ;;
echo "Please note that some caracters might be interpreted by Bash or Awk programs," -s|--nosymbols)
echo "and thus, cannot be used without provoquing errors. Those identified caracters" symb=0
echo "are :" shift
echo ' * ? \ $ { }' ;;
echo -n|--nonumbers)
return 0 numb=0
;; shift
"-s" | "--nosymbols") ;;
symb=0 -u|--noup)
;; maj=0
"-n" | "--nonumbers") shift
numb=0 ;;
;; -l|--nolow)
"-u" | "--noup") min=0
maj=0 shift
;; ;;
"-l" | "--nolow") -e|--extracars)
min=0 extcar="$2"
;; shift 2
"-e"?* | "--extracars"?*) ;;
extcar=$(echo "$opt" | cut -f 2- -d '=') -L|--length)
;; length="$2"
"-L"?* | "--length"?*) if ! [[ $length =~ ^[1-9][0-9]*$ ]]; then
local length=$(echo "$opt" | cut -f 2- -d '=') disp E "The --length parameter requires a positive integer."
if ! [[ $length =~ ^[0-9]+$ ]]; then return 1
disp E "The --length parameter requires a number." fi
shift 2
;;
-o|--occurences|--occurrences)
occurs="$2"
if ! [[ $occurs =~ ^[1-9][0-9]*$ ]]; then
disp E "The --occurences parameter requires a positive integer."
return 1
fi
shift 2
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"genpwd --help\" to display usage."
return 1 return 1
fi ;;
;;
"-o"?* | "--occurences"?*)
local occurs=$(echo "$opt" | cut -f 2- -d '=')
if ! [[ $occurs =~ ^[1-9]+$ ]]; then
disp E "The --occurs parameter requires a number from 1 to 9."
return 1
fi
;;
"-*")
disp E "Unknow parameter ${opt}."
return 1
;;
*)
if ! [[ $opt =~ ^[1-9]+$ ]]; then
disp E "Unknow parameter ${opt}."
return 1
else
nbpwd=$opt
fi
;;
esac esac
done done
# Function selecting a random caracter from the list in parameter if [[ $# -gt 1 ]]; then
pickcar() { disp E "Too many positional arguments. Use only [nb_passwd]."
# When a character is picked we check if it's not appearing already twice return 1
# elsewhere, we choose an other char, to compensate weak bash randomizer fi
while [[ -z $char ]]; do
local char="${1:RANDOM%${#1}:1} $RANDOM" if [[ $# -eq 1 ]]; then
if [[ $(awk -F"$char" '{print NF-1}' <<<"$picked") -gt $occurs ]]; then nbpwd="$1"
unset char if ! [[ $nbpwd =~ ^[1-9][0-9]*$ ]]; then
fi disp E "The number of passwords to generate must be a positive integer."
done return 1
picked+="$char" fi
echo "$char" fi
local carset=""
local unique_carset=""
local ch=""
local i=0
local n=0
local idx=0
local attempts=0
local count=0
local max_attempts=0
local set=""
local char=""
local -a required_sets=()
declare -A seen_chars=()
(( symb )) && {
required_sets+=('!.@#&%/^-_')
carset+='!.@#&%/^-_'
} }
(( numb )) && {
required_sets+=('0123456789')
carset+='0123456789'
}
(( maj )) && {
required_sets+=('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
carset+='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
}
(( min )) && {
required_sets+=('abcdefghijklmnopqrstuvwxyz')
carset+='abcdefghijklmnopqrstuvwxyz'
}
if [[ -n $extcar ]]; then
required_sets+=("$extcar")
carset+="$extcar"
fi
disp I "Generating $nbpwd passwords, please wait..." if [[ -z $carset ]]; then
for n in $(seq 1 $nbpwd); do disp E "No characters are available. Re-enable at least one character class."
{ return 1
local carset='' # store final caracter set to use fi
local picked='' # store already used caracter
local rlength=0 # store already assigned length of caracters
# ?, *, $ and \ impossible to use to my knowledge as it would be interpreted for (( i=0; i<${#carset}; i++ )); do
if [[ $symb == 1 ]]; then ch=${carset:i:1}
pickcar '!.@#&%/^-_' if [[ -z ${seen_chars["$ch"]+x} ]]; then
carset+='!.@#&%/^-_' seen_chars["$ch"]=1
((rlength++)) unique_carset+="$ch"
fi fi
if [[ $numb == 1 ]]; then done
pickcar '0123456789' unset seen_chars
carset+='0123456789' carset="$unique_carset"
((rlength++))
fi
if [[ $maj == 1 ]]; then
pickcar 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
carset+='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
((rlength++))
fi
if [[ $min == 1 ]]; then
pickcar 'abcdefghijklmnopqrstuvwxyz'
carset+='abcdefghijklmnopqrstuvwxyz'
((rlength++))
fi
if [[ -n $extcar ]]; then
pickcar "$extcar"
carset+=$extcar
((rlength++))
fi
# Check if we have enough car to have something viable if (( ${#required_sets[@]} > length )); then
if [[ ${#carset} -lt $length ]]; then disp E "The selected character classes require a longer password."
disp E 'Not enought caracters are authorised for the password length.' return 1
disp E 'Please allow more caracter (preferably) or reduce password lentgh.' fi
return 1
fi
for i in $(seq 1 $(($length - $rlength))); do if (( length > ${#carset} * occurs )); then
pickcar "$carset" disp E "The occurrence limit is too strict for the selected length."
disp E "Please allow more characters or increase --occurences."
return 1
fi
disp I "Generating $nbpwd password(s), please wait..."
for (( n=1; n<=nbpwd; n++ )); do
local -a password_chars=()
local -A char_count=()
max_attempts=$(( ${#carset} * (occurs + 1) + 32 ))
for set in "${required_sets[@]}"; do
attempts=0
while :; do
if (( attempts >= max_attempts )); then
disp E "Unable to satisfy the occurrence limit with the current settings."
return 1
fi
idx=$(( RANDOM % ${#set} ))
char=${set:idx:1}
count=${char_count["$char"]:-0}
if (( count < occurs )); then
char_count["$char"]=$(( count + 1 ))
password_chars+=("$char")
break
fi
((attempts++))
done done
} | sort -R | awk '{printf "%s", $1}' done
unset picked carset rlength
echo while (( ${#password_chars[@]} < length )); do
attempts=0
while :; do
if (( attempts >= max_attempts )); then
disp E "Unable to satisfy the occurrence limit with the current settings."
return 1
fi
idx=$(( RANDOM % ${#carset} ))
char=${carset:idx:1}
count=${char_count["$char"]:-0}
if (( count < occurs )); then
char_count["$char"]=$(( count + 1 ))
password_chars+=("$char")
break
fi
((attempts++))
done
done
for (( i=${#password_chars[@]} - 1; i>0; i-- )); do
idx=$(( RANDOM % (i + 1) ))
char=${password_chars[i]}
password_chars[i]=${password_chars[idx]}
password_chars[idx]=$char
done
printf '%s' "${password_chars[@]}"
printf '\n'
done done
} }
export -f genpwd export -f genpwd
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# pwdscore : score a password quality from 1 to 100
# Usage: pwdscore [options] <password>
pwdscore()
{
local verbose=0
local read_stdin=0
local password=""
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
printf "pwdscore: Score a password from 1 to 100.\n\n"
printf "Usage: pwdscore [options] <password>\n"
printf " pwdscore [options] --stdin\n"
printf " pwdscore [options] # prompt on terminal\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-v, --verbose\t\tShow details about the computed score\n"
printf "\t-i, --stdin\t\tRead the password from standard input\n\n"
printf "Note:\n"
printf " Passwords containing '!' should be quoted, or passed via --stdin.\n"
return 0
;;
-v|--verbose)
verbose=1
shift
;;
-i|--stdin)
read_stdin=1
shift
;;
--)
shift
break
;;
-*)
disp E "Invalid option '$1'. Use \"pwdscore --help\" to display usage."
return 1
;;
*)
break
;;
esac
done
if (( read_stdin )); then
[[ $# -eq 0 ]] || {
disp E "Do not pass a positional password when using --stdin."
return 1
}
IFS= read -r password || true
elif [[ $# -eq 0 ]]; then
if [[ -t 0 ]]; then
read -r -s -p 'Password: ' password < /dev/tty || true
printf '\n' > /dev/tty
else
IFS= read -r password || true
fi
else
[[ $# -eq 1 ]] || {
disp E "Please provide exactly one password to score."
return 1
}
password="$1"
fi
local lower=${password,,}
local length=${#password}
local score=0
local rating="very weak"
local unique_count=0
local i=0 idx=0
local c1=0 c2=0 c3=0
local ch=""
local has_lower=0 has_upper=0 has_digit=0 has_symbol=0
local pool_size=0
local entropy_bits="0.0"
local entropy_score=0
local -A seen=()
if [[ -z $password ]]; then
printf '1\n'
return 0
fi
if (( length >= 20 )); then
score=40
elif (( length >= 16 )); then
score=34
elif (( length >= 12 )); then
score=28
elif (( length >= 8 )); then
score=18
else
score=$(( length * 2 ))
fi
if [[ $password =~ [a-z] ]]; then
has_lower=1
pool_size=$(( pool_size + 26 ))
score=$(( score + 12 ))
fi
if [[ $password =~ [A-Z] ]]; then
has_upper=1
pool_size=$(( pool_size + 26 ))
score=$(( score + 12 ))
fi
if [[ $password =~ [0-9] ]]; then
has_digit=1
pool_size=$(( pool_size + 10 ))
score=$(( score + 12 ))
fi
if [[ $password =~ [^[:alnum:]] ]]; then
has_symbol=1
pool_size=$(( pool_size + 33 ))
score=$(( score + 14 ))
fi
for (( i=0; i<length; i++ )); do
ch=${password:i:1}
if [[ -z ${seen["$ch"]+x} ]]; then
seen["$ch"]=1
unique_count=$(( unique_count + 1 ))
fi
done
score=$(( score + (unique_count * 10) / length ))
if (( pool_size > 1 )); then
entropy_bits=$(awk -v len="$length" -v pool="$pool_size" \
'BEGIN { printf "%.1f", len * (log(pool) / log(2)) }')
entropy_score=$(awk -v bits="$entropy_bits" 'BEGIN {
if (bits < 28) print -35;
else if (bits < 36) print -25;
else if (bits < 60) print -10;
else if (bits < 80) print 0;
else if (bits < 100) print 5;
else print 10;
}')
score=$(( score + entropy_score ))
fi
if [[ $lower =~ (password|admin|root|qwerty|azerty|welcome|letmein|secret|changeme) ]]; then
score=$(( score - 25 ))
fi
if [[ $lower =~ (1234|abcd|qwer|0000|1111|aaaa) ]]; then
score=$(( score - 15 ))
fi
if [[ $password =~ (.)\1\1 ]]; then
score=$(( score - 10 ))
fi
if (( length < 8 )); then
score=$(( score - 10 ))
fi
if (( unique_count * 2 < length )); then
score=$(( score - 10 ))
fi
for (( idx=0; idx<length-2; idx++ )); do
printf -v c1 '%d' "'${lower:idx:1}"
printf -v c2 '%d' "'${lower:idx+1:1}"
printf -v c3 '%d' "'${lower:idx+2:1}"
if (( (c2 == c1 + 1 && c3 == c2 + 1) || \
(c2 == c1 - 1 && c3 == c2 - 1) )); then
score=$(( score - 10 ))
break
fi
done
if (( score < 1 )); then
score=1
elif (( score > 100 )); then
score=100
fi
if (( score >= 90 )); then
rating='excellent'
elif (( score >= 75 )); then
rating='strong'
elif (( score >= 60 )); then
rating='good'
elif (( score >= 40 )); then
rating='fair'
elif (( score >= 20 )); then
rating='weak'
fi
if (( verbose )); then
printf 'Score: %d/100\n' "$score"
printf 'Rating: %s\n' "$rating"
printf 'Length: %d\n' "$length"
printf 'Lowercase: %s\n' "$([[ $has_lower -eq 1 ]] && echo yes || echo no)"
printf 'Uppercase: %s\n' "$([[ $has_upper -eq 1 ]] && echo yes || echo no)"
printf 'Digits: %s\n' "$([[ $has_digit -eq 1 ]] && echo yes || echo no)"
printf 'Symbols: %s\n' "$([[ $has_symbol -eq 1 ]] && echo yes || echo no)"
printf 'Unique chars: %d\n' "$unique_count"
printf 'Entropy: ~%s bits\n' "$entropy_bits"
printf 'Entropy modifier: %+d\n' "$entropy_score"
else
printf '%d\n' "$score"
fi
}
export -f pwdscore
# ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -35,188 +35,399 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Let the rain fall # Generic rain-like engine and presets
# ------------------------------------------------------------------------------
rain() _rain_build_colors()
{ {
show_usage() { local base_color="$1"
echo -e "Usage: rain [OPTIONS]" RAIN_ENGINE_COLORS=()
echo -e ""
echo -e "Options:"
echo -e " -s, --speed NUM Set the drop delay in seconds (default: 0.050)."
echo -e " Lower values = faster rain."
echo -e " -c, --color COLOR Set the color theme (default: white)."
echo -e " -h, --help Display this help message and exit."
echo -e ""
echo -e "Available Colors:"
echo -e " \e[32mgreen\e[0m : The classic Matrix digital rain"
echo -e " \e[34mblue\e[0m : Deep ocean blue gradients"
echo -e " \e[31mred\e[0m : Crimson/Blood rain"
echo -e " \e[33myellow\e[0m : Amber and gold tones"
echo -e " \e[36mcyan\e[0m : Electric cyan/turquoise"
echo -e " white : Greyscale and white (original style)"
echo -e ""
echo -e "Example: rain --color green --speed 0.03"
}
local step_duration=0.050
local base_color="white" # default color scheme, can be overridden by --color
# Analyse arguments
while [[ "$#" -gt 0 ]]; do
case $1 in
-s|--speed)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
step_duration="$2"; shift
else
echo -e "\e[31mError: --speed requires a numeric value.\e[0m"
show_usage && return 1
fi
;;
-c|--color)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
base_color="$2"; shift
else
echo -e "\e[31mError: --color requires a color name.\e[0m"
show_usage && return 1
fi
;;
-h|--help)
show_usage && return 0
;;
*)
echo -e "\e[31mUnknown option: $1\e[0m"
show_usage && return 1
;;
esac
shift
done
# Define colors (256-colors gradients)
local rain_colors=()
case $base_color in case $base_color in
green) # Matrix style green green)
for i in {22..28} {34..40} {46..48}; do rain_colors+=("\e[38;5;${i}m"); done ;; for i in {22..28} {34..40} {46..48}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
blue) # Deep ocean blues blue)
for i in {17..21} {27..33} {39..45}; do rain_colors+=("\e[38;5;${i}m"); done ;; for i in {17..21} {27..33} {39..45}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
red) # Crimson / blood red red)
for i in {52..52} {88..88} {124..124} {160..160} {196..201}; do rain_colors+=("\e[38;5;${i}m"); done ;; for i in {52..52} {88..88} {124..124} {160..160} {196..201}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
yellow) # Amber / gold yellow)
for i in {58..58} {100..100} {142..142} {184..184} {226..229}; do rain_colors+=("\e[38;5;${i}m"); done ;; for i in {58..58} {100..100} {142..142} {184..184} {226..229}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
cyan) # Electric cyan / turquoise cyan)
for i in {30..31} {37..38} {44..45} {50..51}; do rain_colors+=("\e[38;5;${i}m"); done ;; for i in {30..31} {37..38} {44..45} {50..51}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
*) # Greyscale / white (original style) *)
rain_colors=("\e[37m" "\e[37;1m") RAIN_ENGINE_COLORS=("\e[37m" "\e[37;1m")
for i in {244..255}; do rain_colors+=("\e[38;5;${i}m"); done ;; for i in {244..255}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
esac
}
_rain_build_chars()
{
local mode="$1"
local charset="$2"
RAIN_ENGINE_CHARS=()
case "$mode" in
matrix)
case "$charset" in
""|binary)
RAIN_ENGINE_CHARS=("0" "1")
;;
kana|kanji)
# Half-width katakana set, generally rendered as single-cell glyphs.
RAIN_ENGINE_CHARS=("ア" "イ" "ウ" "エ" "オ" "カ" "キ" "ク" "ケ" "コ" "サ" "シ" "ス" "セ" "ソ" "タ" "チ" "ツ" "テ" "ト" "ナ" "ニ" "ヌ" "ネ" "ノ" "ハ" "ヒ" "フ" "ヘ" "ホ" "マ" "ミ" "ム" "メ" "モ" "ヤ" "ユ" "ヨ" "ラ" "リ" "ル" "レ" "ロ" "ワ" "ン")
;;
ascii)
RAIN_ENGINE_CHARS=("0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "A" "B" "C" "D" "E" "F")
;;
*)
disp E "Unknown charset: ${charset} (supported: binary, kana, ascii)."
return 1
;;
esac
;;
*)
RAIN_ENGINE_CHARS=("|" "│" "┃" "┆" "┇" "┊" "┋" "╽" "╿")
;;
esac esac
local exit_st=0 return 0
local rain_cars=("|" "│" "┃" "┆" "┇" "┊" "┋" "╽" "╿") }
local rain_tab=${#rain_cars[@]}
local rain_color_tab=${#rain_colors[@]}
local num_rain_metadata=5
local term_height=$(tput lines)
local term_width=$(tput cols)
local X=0 Y=0 drop_length=0 rain_drop=0
local max_rain_width=0 new_rain_odd=0 falling_odd=0
sigwinch() { _rain_normalize_speed()
term_width=$(tput cols) {
term_height=$(tput lines) local raw_speed="$1"
#step_duration=0.025
((max_rain_width = term_width * term_height / 4)) # Accept integer/floating values. UI scale is centiseconds by default:
((max_rain_height = term_height < 10 ? 1 : term_height / 10)) # 5 -> 0.05s, 2.5 -> 0.025s. Values < 1 are treated as direct seconds
# In percentage # for backward compatibility (e.g. 0.03).
((new_rain_odd = term_height > 50 ? 100 : term_height * 2)) if [[ ! "$raw_speed" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
((new_rain_odd = new_rain_odd * 75 / 100)) return 1
((falling_odd = term_height > 25 ? 100 : term_height * 4)) fi
((falling_odd = falling_odd * 90 / 100))
if awk -v s="$raw_speed" 'BEGIN { exit !(s < 1) }'; then
printf "%s" "$raw_speed"
else
awk -v s="$raw_speed" 'BEGIN { printf "%.3f", s / 100 }'
fi
}
_rain_engine()
{
local step_duration="$1"
local base_color="$2"
local mode="$3"
local charset="$4"
command -v tput >/dev/null 2>&1 || {
disp E "The program 'tput' is required but not installed."
return 1
} }
do_exit() { _rain_build_colors "$base_color"
_rain_build_chars "$mode" "$charset" || return 1
local rain_colors=("${RAIN_ENGINE_COLORS[@]}")
local rain_chars=("${RAIN_ENGINE_CHARS[@]}")
local rain_color_tab=${#rain_colors[@]}
local rain_tab=${#rain_chars[@]}
local matrix_head_color=$'\e[1;97m'
local exit_st=0
local num_rain_metadata=5
local term_height=0 term_width=0
local X=0 Y=0 drop_length=0 rain_drop=0
local max_rain_width=0 max_rain_height=0
local new_rain_odd=0 falling_odd=0
local term_area=0
local frame_sleep="$step_duration"
sigwinch()
{
term_width=$(tput cols)
term_height=$(tput lines)
((term_area = term_width * term_height))
case "$mode" in
matrix)
((max_rain_width = term_area / 3))
((max_rain_height = term_height < 8 ? 1 : term_height / 6))
((new_rain_odd = term_height > 50 ? 100 : term_height * 2))
((new_rain_odd = new_rain_odd * 85 / 100))
((falling_odd = 100))
# Adapt cadence and density to terminal size for smoother rendering.
if ((term_area < 1200)); then
((max_rain_width = term_area / 4))
frame_sleep=$(awk -v s="$step_duration" 'BEGIN { printf "%.3f", s * 1.15 }')
elif ((term_area > 5000)); then
((max_rain_width = term_area / 2))
frame_sleep=$(awk -v s="$step_duration" 'BEGIN { printf "%.3f", s * 0.85 }')
else
frame_sleep="$step_duration"
fi
;;
*)
((max_rain_width = term_area / 4))
((max_rain_height = term_height < 10 ? 1 : term_height / 10))
((new_rain_odd = term_height > 50 ? 100 : term_height * 2))
((new_rain_odd = new_rain_odd * 75 / 100))
((falling_odd = term_height > 25 ? 100 : term_height * 4))
((falling_odd = falling_odd * 90 / 100))
frame_sleep="$step_duration"
;;
esac
}
do_exit()
{
exit_st=1 exit_st=1
} }
do_render() { do_render()
# Clean screen first {
local idx=0 local idx=0 y=0 drop_color="" current_char="" render_color=""
for ((idx = 0; idx < num_rains * num_rain_metadata; idx += num_rain_metadata)); do for ((idx = 0; idx < num_rains * num_rain_metadata; idx += num_rain_metadata)); do
X=${rains[idx]} X=${rains[idx]}
Y=${rains[idx + 1]} Y=${rains[idx + 1]}
drop_length=${rains[idx + 4]} drop_length=${rains[idx + 4]}
for ((y = Y; y < Y + drop_length; y++)); do for ((y = Y; y < Y + drop_length; y++)); do
((y < 1 || y > term_height)) && continue ((y < 1 || y > term_height)) && continue
echo -ne "\e[${y};${X}H " printf "\e[%d;%dH " "$y" "$X"
done done
done done
for ((idx = 0; idx < num_rains * num_rain_metadata; idx += num_rain_metadata)); do for ((idx = 0; idx < num_rains * num_rain_metadata; idx += num_rain_metadata)); do
if ((100 * RANDOM / 32768 < falling_odd)); then if ((100 * RANDOM / 32768 < falling_odd)); then
# Falling
if ((++rains[idx + 1] > term_height)); then if ((++rains[idx + 1] > term_height)); then
# Out of screen, bye sweet <3 rains=("${rains[@]:0:idx}" "${rains[@]:idx+num_rain_metadata:num_rains*num_rain_metadata}")
rains=("${rains[@]:0:idx}"
"${rains[@]:idx+num_rain_metadata:num_rains*num_rain_metadata}")
((num_rains--)) ((num_rains--))
continue continue
fi fi
fi fi
X=${rains[idx]} X=${rains[idx]}
Y=${rains[idx + 1]} Y=${rains[idx + 1]}
rain_drop=${rains[idx + 2]} rain_drop=${rains[idx + 2]}
drop_color=${rains[idx + 3]} drop_color=${rains[idx + 3]}
drop_length=${rains[idx + 4]} drop_length=${rains[idx + 4]}
for ((y = Y; y < Y + drop_length; y++)); do for ((y = Y; y < Y + drop_length; y++)); do
((y < 1 || y > term_height)) && continue ((y < 1 || y > term_height)) && continue
echo -ne "\e[${y};${X}H${drop_color}${rain_drop}" if [[ "$mode" == "matrix" ]]; then
current_char="${rain_chars[rain_tab * RANDOM / 32768]}"
if ((y == Y + drop_length - 1)); then
render_color="$matrix_head_color"
else
render_color="$drop_color"
fi
else
current_char="$rain_drop"
render_color="$drop_color"
fi
printf "\e[%d;%dH%b%s" "$y" "$X" "$render_color" "$current_char"
done done
done done
} }
trap do_exit TERM INT trap do_exit TERM INT
trap sigwinch WINCH trap sigwinch WINCH
# No echo stdin and hide the cursor
stty -echo stty -echo
echo -ne "\e[?25l" printf "\e[?25l"
printf "\e[2J"
echo -ne "\e[2J"
local rains=() local rains=()
local num_rains=0 local num_rains=0
local ch=""
sigwinch sigwinch
while ((exit_st <= 0)); do while ((exit_st <= 0)); do
if (($exit_st <= 0)); then read -r -n 1 -t "$frame_sleep" ch
read -n 1 -t $step_duration ch case "$ch" in
case "$ch" in q|Q)
q | Q)
do_exit do_exit
;; ;;
esac esac
if ((num_rains < max_rain_width)) && ((100 * RANDOM / 32768 < new_rain_odd)); then if ((num_rains < max_rain_width)) && ((100 * RANDOM / 32768 < new_rain_odd)); then
# Need new |, 1-based rain_drop="${rain_chars[rain_tab * RANDOM / 32768]}"
rain_drop="${rain_cars[rain_tab * RANDOM / 32768]}" drop_color="${rain_colors[rain_color_tab * RANDOM / 32768]}"
drop_color="${rain_colors[rain_color_tab * RANDOM / 32768]}" drop_length=$((max_rain_height * RANDOM / 32768 + 1))
drop_length=$((max_rain_height * RANDOM / 32768 + 1)) X=$((term_width * RANDOM / 32768 + 1))
X=$((term_width * RANDOM / 32768 + 1)) Y=$((1 - drop_length))
Y=$((1 - drop_length)) rains=("${rains[@]}" "$X" "$Y" "$rain_drop" "$drop_color" "$drop_length")
rains=("${rains[@]}" "$X" "$Y" "$rain_drop" "$drop_color" "$drop_length") ((num_rains++))
((num_rains++))
fi
# Let rain fall!
do_render
fi fi
done
echo -ne "\e[${term_height};1H\e[0K"
# Show cursor and echo stdin do_render
echo -ne "\e[?25h" done
printf "\e[%d;1H\e[0K" "$term_height"
printf "\e[?25h"
stty echo stty echo
unset exit_st
trap - TERM INT trap - TERM INT
trap - WINCH trap - WINCH
} }
# ------------------------------------------------------------------------------
# Let the rain fall (current style)
# Usage: rain [OPTIONS]
rain()
{
_rain_show_usage()
{
printf "Usage: rain [OPTIONS]\n"
printf "Options:\n"
printf "\t-s, --speed NUM Set speed value (default: 5 => 0.050s).\n"
printf "\t Values >=1 use a /100 scale (5 => 0.05s).\n"
printf "\t Values <1 are interpreted as raw seconds.\n"
printf "\t-c, --color COLOR Set the color theme (default: white).\n"
printf "\t-h, --help Display this help message and exit.\n\n"
printf "Available Colors:\n"
printf "\t\e[32mgreen\e[0m\t: Matrix-like green shades\n"
printf "\t\e[34mblue\e[0m\t: Deep ocean blue gradients\n"
printf "\t\e[31mred\e[0m\t: Crimson/Blood rain\n"
printf "\t\e[33myellow\e[0m\t: Amber and gold tones\n"
printf "\t\e[36mcyan\e[0m\t: Electric cyan/turquoise\n"
printf "\twhite\t: Greyscale and white (original style)\n\n"
printf "Example: rain --color green --speed 3\n"
}
local step_duration=0.050
local base_color="white"
while [[ "$#" -gt 0 ]]; do
case $1 in
-s|--speed)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
step_duration=$(_rain_normalize_speed "$2") || {
disp E "--speed requires a numeric value."
_rain_show_usage
return 1
}
shift
else
disp E "--speed requires a numeric value."
_rain_show_usage
return 1
fi
;;
-c|--color)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
base_color="$2"
shift
else
disp E "--color requires a color name."
_rain_show_usage
return 1
fi
;;
-h|--help)
_rain_show_usage
return 0
;;
--)
shift
break
;;
*)
disp E "Unknown option: $1"
_rain_show_usage
return 1
;;
esac
shift
done
_rain_engine "$step_duration" "$base_color" "rain" ""
}
export -f rain export -f rain
# ------------------------------------------------------------------------------
# Matrix style digital rain
# Usage: matrix [OPTIONS]
matrix()
{
_matrix_show_usage()
{
printf "Usage: matrix [OPTIONS]\n"
printf "Options:\n"
printf "\t-s, --speed NUM Set speed value (default: 3.5 => 0.035s).\n"
printf "\t Values >=1 use a /100 scale (3.5 => 0.035s).\n"
printf "\t Values <1 are interpreted as raw seconds.\n"
printf "\t-c, --color COLOR Set color theme (default: green).\n"
printf "\t-C, --charset SET Character set: binary, kana, ascii (default: binary).\n"
printf "\t-h, --help Display this help message and exit.\n\n"
printf "Example: matrix -C kana -c green --speed 2\n"
}
local step_duration=0.035
local base_color="green"
local charset="binary"
while [[ "$#" -gt 0 ]]; do
case $1 in
-s|--speed)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
step_duration=$(_rain_normalize_speed "$2") || {
disp E "--speed requires a numeric value."
_matrix_show_usage
return 1
}
shift
else
disp E "--speed requires a numeric value."
_matrix_show_usage
return 1
fi
;;
-c|--color)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
case "${2,,}" in
binary|kana|kanji|ascii)
disp W "'${2}' looks like a charset value. Use -C/--charset for clarity."
charset="${2,,}"
;;
*)
base_color="$2"
;;
esac
shift
else
disp E "--color requires a color name."
_matrix_show_usage
return 1
fi
;;
-C|--charset)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
charset="${2,,}"
shift
else
disp E "--charset requires a value."
_matrix_show_usage
return 1
fi
;;
-h|--help)
_matrix_show_usage
return 0
;;
--)
shift
break
;;
*)
disp E "Unknown option: $1"
_matrix_show_usage
return 1
;;
esac
shift
done
_rain_engine "$step_duration" "$base_color" "matrix" "$charset"
}
export -f matrix
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -35,82 +35,161 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Remove host from know_host (name and IP) for the active user # Remove host entries (name and IP) from ~/.ssh/known_hosts for the active user
# ------------------------------------------------------------------------------ # Usage: rmhost <hostname|ip> [hostname2|ip2 [...]]
rmhost() rmhost()
{ {
if [[ "$#" -lt 1 ]]; then local PARSED
disp E "Incorrect number of parameters." local all_users=0
disp E "Usage: rmhost <hostname|ip> [hostname2|ip2 [...]]" local -a known_hosts_files=()
PARSED=$(getopt -o ha --long help,all-users -n 'rmhost' -- "$@")
if [[ $? -ne 0 ]]; then return 1; fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "rmhost: Remove host/IP from known_hosts files.\n\n"
printf "Usage: rmhost [--all-users] <hostname|ip> [hostname2|ip2 ...]\n\n"
printf "Options:\n"
printf " -a, --all-users Remove entries from all local users when run as root\n"
printf " -h, --help Display this help screen\n"
return 0
;;
-a|--all-users)
all_users=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"rmhost --help\" to display usage."
return 1
;;
esac
done
[[ $# -eq 0 ]] && {
disp E "Missing argument. Use 'rmhost --help' for usage."
return 1 return 1
}
command -v ssh-keygen >/dev/null 2>&1 || {
disp E "ssh-keygen is not installed."
return 127
}
if (( all_users )); then
[[ ${EUID:-$(id -u)} -eq 0 ]] || {
disp E "Option --all-users is only available when run as root."
return 1
}
while IFS=: read -r _ _ _ _ _ home _; do
[[ -n $home && -f $home/.ssh/known_hosts ]] || continue
known_hosts_files+=("$home/.ssh/known_hosts")
done < /etc/passwd
[[ -f /etc/ssh/ssh_known_hosts ]] && \
known_hosts_files+=("/etc/ssh/ssh_known_hosts")
[[ ${#known_hosts_files[@]} -gt 0 ]] || {
disp W "No known_hosts files found for local users."
return 0
}
else
known_hosts_files=("${HOME}/.ssh/known_hosts")
fi fi
while [[ $1 ]]; do for target in "$@"; do
local hst=$1 && shift local hst="$target"
isipv4 "$hst" >/dev/null local ip=""
local v4=$? local v4=1
isipv6 "$hst" >/dev/null local v6=1
local v6=$?
isipv4 "$hst" >/dev/null 2>&1; v4=$?
isipv6 "$hst" >/dev/null 2>&1; v6=$?
if [[ $v4 -eq 0 || $v6 -eq 0 ]]; then if [[ $v4 -eq 0 || $v6 -eq 0 ]]; then
local ip=$hst ip="$hst"
unset hst hst=""
fi fi
unset v4 v6
if [[ ! $ip && $hst ]]; then if [[ -z ${ip:-} && -n ${hst:-} ]]; then
if ! ip=$(host "$hst" 2>/dev/null | awk '/has address/ {print $NF; exit}'); then if command -v host >/dev/null 2>&1; then
disp E "Impossible to extract IP from hostname." && ip=$(host "$hst" 2>/dev/null |
return 1 awk '/has address|has IPv6 address/ {print $NF; exit}')
elif command -v getent >/dev/null 2>&1; then
ip=$(getent ahosts "$hst" 2>/dev/null | awk 'NR == 1 {print $1; exit}')
else
disp W "No resolver tool found; removing hostname only for '$hst'."
fi fi
[[ -z $ip ]] && {
disp E "Impossible to extract IP from hostname." [[ -z ${ip:-} ]] && \
return 1; disp W "Could not resolve IP for '$hst'; removing hostname only."
}
fi fi
if [[ $hst ]]; then local known_hosts_file=""
disp I "Removing host $hst from ssh known_host..." for known_hosts_file in "${known_hosts_files[@]}"; do
ssh-keygen -R $hst >/dev/null if [[ -n ${hst:-} ]]; then
fi disp I "Removing host $hst from $known_hosts_file..."
if [[ $ip ]]; then if ! ssh-keygen -R "$hst" -f "$known_hosts_file" >/dev/null 2>&1; then
disp I "Removing IP $ip from ssh known_host..." disp W "No known_hosts entry found for '$hst' in '$known_hosts_file'."
ssh-keygen -R $ip >/dev/null fi
fi fi
unset hst ip if [[ -n ${ip:-} ]]; then
disp I "Removing IP $ip from $known_hosts_file..."
if ! ssh-keygen -R "$ip" -f "$known_hosts_file" >/dev/null 2>&1; then
disp W "No known_hosts entry found for '$ip' in '$known_hosts_file'."
fi
fi
done
done done
} }
export -f rmhost export -f rmhost
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Login root via SSH on the given machine # Login root via SSH on the given machine
# ------------------------------------------------------------------------------ # Usage: ssr <server [ssh options]>
ssr() ssr()
{ {
for opt in $@; do case "${1:-}" in
case $opt in -h|--help)
"-h" | "--help") printf "ssr: SSH into a server as root.\n\n"
echo "ssr: do a root user ssh login." printf "Usage: ssr <server> [ssh_options...]\n\n"
echo printf "Notes:\n"
echo "Usage: ssr <server [ssh options]>" printf " The first argument is the target server.\n"
printf " All remaining arguments are passed directly to ssh.\n\n"
printf "Examples:\n"
printf " ssr srv01\n"
printf " ssr srv01 -p 2222\n"
printf " ssr srv01 -i ~/.ssh/id_ed25519 -J bastion\n"
return 0 return 0
;; ;;
esac esac
done
command -v ssh >/dev/null 2>&1 || { command -v ssh >/dev/null 2>&1 || {
disp E "ssh is not installed." disp E "ssh is not installed."
return 127 return 127
} }
[[ ! $1 ]] &&
disp E "Please specify the server you want to log in." &&
return 1
local srv=$1 && shift [[ $# -eq 0 || -z ${1:-} ]] && {
disp E "Please specify the server you want to log in."
return 1
}
local srv=$1
shift
ssh -Y root@"$srv" "$@" ssh -Y root@"$srv" "$@"
} }
export -f ssr export -f ssr
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -39,95 +39,374 @@ export UPDT_URL="$BASE_URL/raw/branch/master"
export ARCH_URL="$BASE_URL/archive/master.tar.gz" export ARCH_URL="$BASE_URL/archive/master.tar.gz"
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Check for profile updates # Check whether a newer profile version is available
# ------------------------------------------------------------------------------ # Usage: check_updates [-q]
# If -q is specified, the function will operate in quiet mode (internal use only)
check_updates() check_updates()
{ {
if [[ $1 == "-q" ]]; then local quiet=0 result=5 PARSED
# Quiet mode is mostly used internally when profile_upgrade is called local vfile="" lastver=""
quiet=1
PARSED=$(getopt -o hq --long help,quiet -n 'check_updates' -- "$@")
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"check_updates --help\" to display usage."
return 2
fi fi
[[ -n $quiet ]] && disp I "Checking for updates..." eval set -- "$PARSED"
local vfile="/tmp/version"
wget "$UPDT_URL/version" -O $vfile >/dev/null 2>&1 || { while true; do
disp E "Can't download version file, impossible to proceed!" case "$1" in
-h|--help)
printf "check_updates: Check whether a newer profile version is available.\n\n"
printf "Usage: check_updates [-q|--quiet]\n"
printf "This command only checks availability; it does not modify the installation.\n"
return 0
;;
-q|--quiet)
quiet=1
shift
;;
--)
shift
break
;;
*)
break
;;
esac
done
(( quiet != 1 )) && disp I "Checking for updates..."
vfile=$(mktemp /tmp/profile_version.XXXXXX) || {
disp E "Failed to create a temporary file."
return 4
}
dwl "$UPDT_URL/version" "$vfile" >/dev/null 2>&1 || {
rm -f "$vfile"
disp E "Cannot download version file; unable to continue."
return 5 return 5
} }
if [[ -s $vfile ]]; then if [[ -s $vfile ]]; then
local lastver=$(cat $vfile) lastver=$(<"$vfile")
if [[ $lastver != $PROFVERSION ]]; then if [[ "$lastver" != "$PROFVERSION" ]]; then
disp I "You have version $PROFVERSION installed. Version $lastver is available." disp I "Installed: $PROFVERSION. Available: $lastver."
[[ $quiet ]] && disp I "You should upgrade to last version when possible." (( quiet != 1 )) && disp I "You should upgrade when possible."
result=1 result=1
else else
[[ -n $quiet ]] && disp I "Your version is up-to-date." (( quiet != 1 )) && disp I "Your version is up-to-date."
result=0 result=0
fi fi
rm -f $vfile rm -f "$vfile"
else else
disp E "Impossible to read temporary file, impossible to proceed." rm -f "$vfile"
disp E "Temporary file is unreadable; unable to continue."
fi fi
unset lastver vfile
return $result return $result
} }
export -f check_updates
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Apply update to profile # Apply the available profile upgrade
# ------------------------------------------------------------------------------ # Usage: profile_upgrade [options]
profile_upgrade() profile_upgrade()
{ {
if check_updates -q; then local PARSED
disp "No update available." local check_rc=0 dry_run=0 force_git=0 switch_to_git=0
return 0 local archive_file="" tmpbase="" use_archive=0 branch=""
local tmpdir="" archive="" extracted_root=""
PARSED=$(getopt -o hf:t:nFb:g --long help,file:,tmpdir:,dry-run,force,branch:,switch-to-git -n 'profile_upgrade' -- "$@")
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"profile_upgrade --help\" to display usage."
return 2
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "profile_upgrade: Apply the available profile upgrade.\n\n"
printf "Usage: profile_upgrade [options]\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-f, --file ARCHIVE\tUse a local archive file for the upgrade\n"
printf "\t-t, --tmpdir DIR\tCreate the temporary working directory under DIR\n"
printf "\t-b, --branch NAME\tUse NAME as the target Git branch\n"
printf "\t-g, --switch-to-git\tReplace current install with a fresh Git clone\n"
printf "\t-n, --dry-run\t\tDisplay what would be done without changing anything\n"
printf "\t-F, --force\t\tDiscard local changes before upgrading\n\n"
printf "If the profile is installed from Git, the upgrade uses 'git pull'.\n"
printf "Otherwise, it downloads or applies an archive and refreshes the files.\n"
return 0
;;
-f|--file)
archive_file="$2"
use_archive=1
shift 2
;;
-t|--tmpdir)
tmpbase="$2"
shift 2
;;
-b|--branch)
branch="$2"
shift 2
;;
-g|--switch-to-git)
switch_to_git=1
shift
;;
-n|--dry-run)
dry_run=1
shift
;;
-F|--force)
force_git=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"profile_upgrade --help\" to display usage."
return 1
;;
esac
done
if (( ! use_archive && ! switch_to_git )); then
check_updates -q
check_rc=$?
if (( check_rc == 0 )); then
disp I "No update available."
return 0
elif (( check_rc > 1 )); then
disp E "Unable to check whether an update is available."
return "$check_rc"
fi
fi fi
if [[ -s $MYPATH/profile.sh ]]; then if [[ ! -s $MYPATH/profile.sh ]]; then
disp E "Installation path detection failed, cannot upgrade automatically." disp E "Install path detection failed; cannot upgrade automatically."
return 1 return 1
fi fi
if [[ -d $MYPATH/.git ]]; then if [[ -d $MYPATH/.git ]] && (( use_archive )) && (( ! force_git )); then
disp E "Refusing archive upgrade on a Git install without --force."
return 1
fi
if (( switch_to_git )); then
command -v git >/dev/null 2>&1 || {
disp E "Git is required to switch this install to a Git clone."
return 3
}
if (( dry_run )); then
disp I "[dry-run] rm -rf \"$MYPATH\"/.git"
disp I "[dry-run] git clone "$BASE_URL" \"$MYPATH\""
[[ -n "$branch" ]] && disp I "[dry-run] git -C \"$MYPATH\" checkout "$branch""
return 0
fi
if [[ -d $MYPATH/.git ]]; then
disp W "Git repository already present; no switch is needed."
else
local backup_dir="${MYPATH}.pre-git.$$.bak"
mv "$MYPATH" "$backup_dir" || {
disp E "Failed to move current install out of the way."
return 3
}
git clone "$BASE_URL" "$MYPATH" || {
disp E "Git clone failed; previous install kept in $backup_dir."
mv "$backup_dir" "$MYPATH" 2>/dev/null || true
return 3
}
[[ -n "$branch" ]] && (
cd "$MYPATH" && git checkout "$branch"
) || true
disp I "Switched installation to Git source."
disp I "Previous install kept in $backup_dir."
return 0
fi
fi
if [[ -d $MYPATH/.git ]] && (( ! use_archive )); then
disp I "Git installation detected, applying git pull." disp I "Git installation detected, applying git pull."
local curdir=$(pwd) command -v git >/dev/null 2>&1 || {
cd $MYPATH disp E "Git is required for this upgrade but is not available."
git pull || { return 3
disp E "Git pull failed, upgrade not applyed."
cd "$curdir"
return 2
} }
disp I "Successfully upgraded using git." pushd "$MYPATH" >/dev/null || {
cd "$curdir" disp E "Failed to change directory to $MYPATH."
return 3
}
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || {
disp E "Install directory is not a valid Git working tree."
popd >/dev/null || return 1
return 3
}
if ! git diff --quiet || ! git diff --cached --quiet || [[ -n $(git ls-files --others --exclude-standard) ]]; then
if (( force_git )); then
disp W "Force mode: local Git changes and untracked files will be lost."
if (( dry_run )); then
disp I "[dry-run] git fetch --all --prune"
disp I "[dry-run] git reset --hard HEAD"
disp I "[dry-run] git clean -fd"
else
git fetch --all --prune || {
disp E "Git fetch failed, upgrade not applied."
popd >/dev/null || return 1
return 4
}
git reset --hard HEAD || {
disp E "Git reset failed, upgrade not applied."
popd >/dev/null || return 1
return 4
}
git clean -fd || {
disp E "Git clean failed, upgrade not applied."
popd >/dev/null || return 1
return 4
}
fi
else
disp W "The Git working tree contains local changes."
disp W "Consider committing or stashing them before upgrading, or use --force."
disp W "Upgrade may fail if the changes conflict with the upgrade."
fi
fi
if [[ -n "$branch" ]]; then
if (( dry_run )); then
disp I "[dry-run] git fetch origin $branch"
disp I "[dry-run] git checkout $branch"
else
git fetch origin "$branch" || {
disp E "Git fetch failed for branch $branch."
popd >/dev/null || return 1
return 2
}
git checkout "$branch" || {
disp E "Git checkout failed for branch $branch."
popd >/dev/null || return 1
return 2
}
fi
fi
if (( dry_run )); then
if [[ -n "$branch" ]]; then
disp I "[dry-run] git pull origin $branch"
else
disp I "[dry-run] git pull"
fi
else
if [[ -n "$branch" ]]; then
git pull origin "$branch" || {
disp E "Git pull failed, upgrade not applied."
popd >/dev/null || return 1
return 2
}
else
git pull || {
disp E "Git pull failed, upgrade not applied."
popd >/dev/null || return 1
return 2
}
fi
disp I "Successfully upgraded using git."
fi
popd >/dev/null || return 1
else else
disp I "No Git detected. Downloading and applying upgrade from archive..." if (( use_archive )); then
local tmpdir="/tmp/profile_upg.$$" [[ -r "$archive_file" ]] || {
mkdir -p "$tmpdir" || { disp E "Local archive '$archive_file' is missing or unreadable."
disp E "Failed to create temporary directory." return 4
return 4 }
} disp I "Using local archive $archive_file."
else
disp W "No Git repo found. Git is the recommended source."
disp I "Applying upgrade from archive..."
fi
local archive="$tmpdir/profile.tar.gz" if [[ -n "$tmpbase" ]]; then
wget -q "$ARCH_URL" -O "$archive" || { if (( dry_run )); then
disp E "Failed to download archive." disp I "[dry-run] mkdir -p \"$tmpbase\""
disp I "[dry-run] mktemp -d \"$tmpbase/profile_upg.XXXXXX\""
tmpdir="$tmpbase/profile_upg.DRYRUN"
else
mkdir -p "$tmpbase" || {
disp E "Failed to create temporary directory base $tmpbase."
return 5
}
tmpdir=$(mktemp -d "$tmpbase/profile_upg.XXXXXX") || {
disp E "Failed to create temp working directory under $tmpbase."
return 5
}
fi
else
if (( dry_run )); then
disp I "[dry-run] mktemp -d /tmp/profile_upg.XXXXXX"
tmpdir="/tmp/profile_upg.DRYRUN"
else
tmpdir=$(mktemp -d /tmp/profile_upg.XXXXXX) || {
disp E "Failed to create temporary directory."
return 5
}
fi
fi
if (( use_archive )); then
archive="$archive_file"
else
archive="$tmpdir/profile.tar.gz"
if (( dry_run )); then
disp I "[dry-run] dwl \"$ARCH_URL\" \"$archive\""
else
dwl "$ARCH_URL" "$archive" || {
disp E "Failed to download archive."
rm -rf "$tmpdir"
return 6
}
fi
fi
if (( dry_run )); then
disp I "[dry-run] tar -xzf \"$archive\" -C \"$tmpdir\""
disp I "[dry-run] cp -a <extracted_profile>/. \"$MYPATH\"/"
else
tar -xzf "$archive" -C "$tmpdir" || {
disp E "Archive extraction failed."
rm -rf "$tmpdir"
return 7
}
extracted_root=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d ! -name '.*' | head -n 1)
if [[ -z "$extracted_root" ]]; then
disp E "Could not find extracted profile files."
rm -rf "$tmpdir"
return 8
fi
disp I "Installing new version..."
cp -a "$extracted_root"/. "$MYPATH"/ || {
disp E "Failed to copy new files into $MYPATH."
rm -rf "$tmpdir"
return 9
}
disp I "Upgrade complete. Please log out and log in again."
rm -rf "$tmpdir" rm -rf "$tmpdir"
return 5 fi
}
tar -xzf "$archive" -C "$tmpdir" || {
disp E "Archive extraction failed."
rm -rf "$tmpdir"
return 6
}
disp I "Installing new version..."
cp -r "$tmpdir"/profile/* "$MYPATH"/ || {
disp E "Failed to copy new files to $MYPATH."
rm -rf "$tmpdir"
return 7
}
disp I "Upgrade complete. You should now logout and login again."
rm -rf "$tmpdir"
fi fi
} }
export -f profile_upgrade
# ------------------------------------------------------------------------------
# EOF # EOF

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Begin profile # Begin profile
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org> # Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details. # Protected by the BSD3 license. Please read bellow for details.
# #
# * Redistribution and use in source and binary forms, # * Redistribution and use in source and binary forms,
@@ -35,15 +35,21 @@
# * OF SUCH DAMAGE. # * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
if [[ ! $SHELL =~ bash|zsh ]]; then if [[ ! $SHELL =~ bash ]]; then
echo "That environment script is designed to be used with bash or zsh being the shell." echo "That environment script is designed to be used with bash being the shell."
echo "Please consider using bash or zsh instead, or patch me ;)!" echo "Please consider using bash to enjoy our features!"
return 1 return 1
fi fi
# Required for associative arrays (4.0+) and namerefs (4.3+)
if ((BASH_VERSINFO[0] < 4)) || [[ ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -lt 3 ]]; then
echo "[ Error ] This profile requires Bash 4.3 or higher."
echo "Current version: $BASH_VERSION"
return 1 2>/dev/null || exit 1
fi
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# path* : private functions for PATH variable management # path* : private functions for PATH variable management
# ------------------------------------------------------------------------------
pathremove() pathremove()
{ {
local IFS=':' local IFS=':'
@@ -71,6 +77,110 @@ pathappend()
local pathvar=${2:-PATH} local pathvar=${2:-PATH}
export $pathvar="${!pathvar:+${!pathvar}:}$1" export $pathvar="${!pathvar:+${!pathvar}:}$1"
} }
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Configuration file parser
parse_conf()
{
local config_file="$1"
local current_section=""
local line key value
[[ ! -f "$config_file" ]] && return 1
while IFS='=' read -r key value || [[ -n "$key" ]]; do
# Internal trimming (removes leading/trailing whitespace & CR)
key="${key%"${key##*[![:space:]]}"}"
key="${key#"${key%%[![:space:]]*}"}"
key="${key%$'\r'}" # Strip potential Windows line endings
# Skip comments and empty lines
[[ -z "$key" || "$key" =~ ^[#\;] ]] && continue
# Section Detection: [section_name]
if [[ "$key" =~ ^\[([a-zA-Z0-9_]+)\]$ ]]; then
current_section="${BASH_REMATCH[1]}"
declare -g -A "CONF_$current_section"
continue
fi
# Secure Assignment (if inside a section)
if [[ -n "$current_section" ]]; then
# Clean the value
value="${value%"${value##*[![:space:]]}"}"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%$'\r'}"
# Protect against command injection by disallowing certain characters in keys
value="${value//\`/}"
value="${value//\$\(/}"
# Correctly interpretet internal variables (e.g. $HOME)
if [[ "$value" == *\$* ]]; then
value=$(envsubst <<< "$value")
fi
# Strip quotes (handling both " and ')
value="${value%\"}"; value="${value#\"}"
value="${value%\'}"; value="${value#\'}"
# Use a nameref for safe, eval-free assignment
local -n current_array="CONF_$current_section"
current_array["$key"]="$value"
fi
done < "$config_file"
}
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Load command aliases from configuration
load_alias()
{
local section_name="CONF_$1"
# Check if the associative array exists using declare -p
[[ "$(declare -p "$section_name" 2>/dev/null)" != "declare -A"* ]] && return 1
# Create a nameref to the section array
local -n current_aliases="$section_name"
# Iterate safely over the keys of the associative array
for key in "${!current_aliases[@]}"; do
local cmd="${current_aliases[$key]}"
# Extract the base command (first word) safely without awk
local base_cmd="${cmd%% *}"
# Only alias if the base command is executable
if command -v "$base_cmd" >/dev/null 2>&1; then
alias "$key"="$cmd"
fi
done
}
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Load configuration values as environment variables
load_conf()
{
local section_name="CONF_$1"
[[ "$(declare -p "$section_name" 2>/dev/null)" != "declare -A"* ]] && return 1
local -n current_vars="$section_name"
for key in "${!current_vars[@]}"; do
# Export the key/value pair as a standard shell variable
# We use 'export' directly; Bash handles the assignment safely here
export "$key"="${current_vars[$key]}"
done
}
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@@ -79,8 +189,10 @@ pathappend()
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Store script's path (realpath -s resolve symlinks if profile.sh is a symlink) # Store script's path (realpath -s resolve symlinks if profile.sh is a symlink)
# Because we're more likely to be sourced, we use BASH_SOURCE to get the path
# of the sourced file instead of $0
if [[ -z "$PROFILE_PATH" ]]; then if [[ -z "$PROFILE_PATH" ]]; then
export MYPATH=$(dirname "$(realpath -s "$0")") export MYPATH=$(dirname "$(realpath -s "${BASH_SOURCE[0]}")")
else else
export MYPATH="$PROFILE_PATH" export MYPATH="$PROFILE_PATH"
fi fi
@@ -101,41 +213,14 @@ export PROFVERSION=$(cat "$MYPATH"/version)
if [[ $EUID -eq 0 ]]; then if [[ $EUID -eq 0 ]]; then
pathappend /sbin:/usr/sbin pathappend /sbin:/usr/sbin
fi fi
[[ -d /share/services/gestparc ]] && pathappend /share/services/gestparc
[[ -d ~/bin ]] && pathappend ~/bin [[ -d ~/bin ]] && pathappend ~/bin
[[ -d ~/.local/bin ]] && pathappend ~/.local/bin [[ -d ~/.local/bin ]] && pathappend ~/.local/bin
# ------------------------------------------------------------------------------ # Parse and load general configuration
# Default values are set here and will be overloaded with config file if any export PROFILE_CONF="$MYPATH/profile.conf"
# ------------------------------------------------------------------------------ parse_conf "$PROFILE_CONF"
load_conf system # Load Bash system behavior configuration (history, pager, etc.)
# Set bash history load_conf general # General purpose configuration (compilation flags, etc.)
export HISTSIZE=50000
export HISTIGNORE="&:[bf]g:exit"
# Set default pager
export PAGER=less
# More colors
export TERM=xterm-256color
# Set some compiling values
export CFLAGS="-O2 -pipe -march=native"
export MAKEFLAGS='-j12'
export PKGSOURCES='/share/src/archives'
# Default city for weather forcast
export DEFAULT_CITY="Toulouse"
# ------------------------------------------------------------------------------
# Default values could be altered after this line
# ------------------------------------------------------------------------------
# Load global configuration
[[ -f $MYPATH/etc/profile.conf ]] && . $MYPATH/etc/profile.conf
# Load personal configuration
[[ -f ~/.profile.conf ]] && . ~/.profile.conf
# Load module scripts # Load module scripts
for script in $MYPATH/profile.d/*.sh; do for script in $MYPATH/profile.d/*.sh; do
@@ -152,30 +237,7 @@ done
if [[ $INTERACTIVE ]]; then if [[ $INTERACTIVE ]]; then
# For compiling (as we often compile with LFS/0linux...) # For compiling (as we often compile with LFS/0linux...)
#Aliases #Aliases
alias ll='ls -laFh --color=auto' load_alias aliases
alias la='ls -Ah --color=auto'
alias l='ls -CF --color=auto'
alias ls='ls --color=auto'
alias grep='grep --color=auto'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias qfind="find . -name "
alias mkck='make check'
alias mkin='make install'
alias mkdin='make DESTDIR=$PWD/dest-install install'
alias ssh='ssh -Y'
alias wget='wget -c' # resume mode by default
alias myip='curl ip.appspot.com'
# Human readable by default
alias df='df -H'
alias du='du -ch'
alias sdu='du -sk ./* | sort -n'
alias hdu='du -hs ./* | sort -H'
# Define PS1 # Define PS1
trap 'timer_start' DEBUG trap 'timer_start' DEBUG
@@ -183,7 +245,7 @@ if [[ $INTERACTIVE ]]; then
# Set default language # Set default language
setfr setfr
showinfo showinfo && printf "\n"
check_updates -q check_updates -q
disp I "Profile version $PROFVERSION chargé..." disp I "Profile version $PROFVERSION chargé..."
fi fi

View File

@@ -1 +1 @@
3.6.0 3.95.2-4_beta_2