Files
profile/profile.d/filefct.sh
2026-03-25 16:10:41 +01:00

727 lines
24 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# expandlist : treat wildcards in a file/directory list
# Usage: expandlist <item1 [item2 ... itemN]>
expandlist()
{
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
printf "expandlist: Wraps a list of items in double quotes.\n\n"
printf "Usage: expandlist <item1 [item2 ... itemN]>\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
return 0
fi
local result=""
for item in "$@"; do
for content in "$item"; do
result+="\"$content\" "
done
done
echo $result
}
export -f expandlist
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Clean a directory or a 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)
local 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)
local outshell=1
shift
;;
-f|--force)
local 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"
unset recursive force
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
rm $rmopt $f
else
echo "rm $rmopt $f"
fi
done
done
unset outshell dirlist dellist findopt rmopt
}
export -f clean
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Create a directory then goes inside
# Usage: mcd <directory>
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
disp E "Missing parameter. Use \"mcd --help\" to display usage."
return 1
fi
mkdir -pv "$1" && cd "$1" || {
printf "Failed create and/or change directory.\n"
return 1
}
}
export -f mcd
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Rename all files in current directory to replace spaces with _
# 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()
{
local lst=""
local PARSED
PARSED=$(getopt -o hr:c::vs --long help,recursive,subst-char::,verbose,shell -n 'rmspc' -- "$@")
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"rmspc --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "rmspc: remove spaces from all filenames in current directories\n\n"
printf "Usage: rmspc [option]\n\n"
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"
printf "\t-c, --subst-char\tChange the replacement character (default is underscore)\n"
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"
printf " replaced with nothing (concatenation).\n"
return 0
;;
-r|--recursive)
local recurs=1
shift
;;
-c|--subst-char)
# Handle optional argument for short/long options
case "$2" in
"")
substchar=""
;;
*)
substchar="$2"
;;
esac
shift 2
;;
-v|--verbose)
local verb=1
shift
;;
-s|--shell)
local shell=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid parameter, use \"rmspc --help\" to display options list"
echo
return 1
;;
esac
done
[[ ! $substchar ]] && substchar="_"
[[ $substchar == "none" ]] && local substchar=""
[[ $verb ]] && local mvopt="-v"
shopt -s nullglob
for f in *; do
[[ $recurs ]] && [[ -d "$f" ]] && (
[[ $verb ]] && disp I "Entering directory $(pwd)/$f ..."
local lastdir=$f
pushd "$f" >/dev/null
rmspc ${recurs:+-r} ${substchar:+-c "$substchar"} ${verb:+-v} ${shell:+-s}
popd >/dev/null
[[ $verb ]] && disp I "Leaving directory $(pwd)/$lastdir"
unset lastdir
)
if [[ "$f" == *" "* ]]; then
local newf="${f// /${substchar}}"
[[ "$f" == "$newf" ]] && continue # protection but should never happen
if [[ -n $shell ]]; then
echo "mv ${mvopt:+$mvopt }\"$f\" \"$newf\""
else
mv ${mvopt:+$mvopt} "$f" "$newf" || {
disp E "Failed renaming \"$f\" to \"$newf\"."
continue
}
fi
fi
done
unset lst substchar verb shell newf command mvopt
}
export -f rmspc
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# display stats about a file structure
# 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()
{
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 PARSED
# 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
-h|--help)
printf "Usage: file_stats [options] [path]\n\n"
printf "Options:\n"
printf "\t-H, --human\t\tHuman readable sizes\n"
printf "\t-d, --details\t\tShow detailed histogram\n"
printf "\t-m, --average\t\tShow only average size\n"
printf "\t-M, --median\t\tShow only median size\n"
printf "\t-c, --count\t\tShow only file count\n"
printf "\t-t, --total\t\tShow only total size\n"
printf "\t-a, --all\t\tShow all (human + details)\n"
printf "\t-x, --ext [ext]\t\tFilter by extension\n"
printf "\t-X, --ext-list [list]\tFilter by comma-separated list\n"
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
shift
done
[[ -n "$1" ]] && path="$1"
# Prepare find filters
local find_cmd=(find "$path" -type f)
# Extension simple
if [[ -n "$ext_filter" ]]; then
find_cmd+=(-iname "*.$ext_filter")
fi
# Extension liste
if [[ -n "$ext_list" ]]; then
IFS=',' read -ra exts <<< "$ext_list"
find_cmd+=('(')
for i in "${!exts[@]}"; do
[[ $i -ne 0 ]] && find_cmd+=(-o)
find_cmd+=(-iname "*.${exts[$i]}")
done
find_cmd+=(')')
fi
# Taille min/max (à évaluer en octets)
if [[ -n "$min_size" ]]; then
find_cmd+=(-size +"$(numfmt --from=iec "$min_size")"c)
fi
if [[ -n "$max_size" ]]; then
find_cmd+=(-size -"$(( $(numfmt --from=iec "$max_size") + 1 ))"c)
fi
# Exécution
"${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) {
split("B KiB MiB GiB TiB", units)
i = 1
while (x >= 1024 && i < 5) {
x /= 1024
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
total += $1
if (min == "" || $1 < min) min = $1
if (max == "" || $1 > max) max = $1
if ($1 == 0) bucket[0]++
else {
b = int(log($1)/log(1024))
bucket[b]++
}
}
END {
count = NR
if (count == 0) {
print "No files found."
exit
}
average = total / count
# Median calculation: exact using sorted array values
if (count % 2 == 1) {
median = sizes[(count + 1) / 2]
} else {
idx = count / 2
median = (sizes[idx] + sizes[idx + 1]) / 2
}
if (only_avg) out("Average size", average, 1)
else if (only_med) out("Median size", median, 1)
else if (only_count) out("Number of files", count, 0)
else if (only_total) out("Total size", total, 1)
else {
if (show_all || human || details) {
printf "Statistics for \"%s\"\n", path
printf "-------------------------\n"
}
out("Number of files", count, 0)
out("Total size", total, 1)
out("Average size", average, 1)
out("Median size", median, 1)
out("Minimum size", min, 1)
out("Maximum size", max, 1)
}
if (details) {
print "\nSize histogram:"
# Use a separate array for the loop to avoid collision
for (b in bucket) {
# Pre-calculate label parts
# 1024^0 = 1 (B), 1024^1 = 1K, etc.
low = (b == 0) ? 0 : (1024^b)
high = 1024^(b+1)
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]
}
}
}'
}
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 no_change=0 one_fs=0
local PARSED
PARSED=$(getopt -o hd:l:x --long help,details,one-fs,limit: -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
;;
-n|--no-change)
no_change=1
shift
;;
-l|--limit)
limit="$2"
shift 2
;;
--)
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" "-type" "f")
(( one_fs )) && find_args+=("-xdev")
# 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 read -r size path; do
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 no_change=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 no_change=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