344 lines
11 KiB
Bash
344 lines
11 KiB
Bash
#!/usr/bin/env bash
|
||
# ------------------------------------------------------------------------------
|
||
# Copyright (c) 2013-2022 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
|
||
# ------------------------------------------------------------------------------
|
||
expandlist()
|
||
{
|
||
local result=""
|
||
for item in "$1"; do
|
||
for content in "$item"; do
|
||
result+="\"$content\" "
|
||
done
|
||
done
|
||
echo $result
|
||
}
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Clean a directory or a tree from temporary or backup files
|
||
# ------------------------------------------------------------------------------
|
||
clean()
|
||
{
|
||
for opt in $@; do
|
||
case $opt in
|
||
"-r" | "--recurs")
|
||
local recursive=1
|
||
;;
|
||
|
||
"-h" | "--help")
|
||
echo "clean: erase backup files in the given directories."
|
||
echo
|
||
echo "Usage: clean [option] [directory1] [...[directoryX]]"
|
||
echo
|
||
echo "Options:"
|
||
echo " -h, --help Display that help screen"
|
||
echo " -r, --recurs Do a recursive cleaning"
|
||
echo " -f, --force Do not ask for confirmation (use with care)"
|
||
echo " -s, --shell Do nothing and display what will be executed"
|
||
echo
|
||
return 0
|
||
;;
|
||
|
||
"-s" | "--shell")
|
||
local outshell=1
|
||
;;
|
||
|
||
"-f" | "--force")
|
||
local force=1
|
||
;;
|
||
|
||
"-"*)
|
||
disp E "Invalid option, use \"clean --help\" to display usage."
|
||
echo
|
||
return 1
|
||
;;
|
||
|
||
*)
|
||
local dirlist="$dirlist $opt"
|
||
;;
|
||
esac
|
||
done
|
||
|
||
[[ ! $dirlist ]] && local dirlist=$(pwd)
|
||
|
||
[[ ! $recursive ]] && local findopt="-maxdepth 1"
|
||
[[ ! $force ]] && local rmopt="-i"
|
||
unset recursive force
|
||
|
||
for dir in $dirlist; do
|
||
local dellist=$(find "$dir" $findopt -type f -name "*~" -o -name "#*#" \
|
||
-o -name "*.bak" -o -name ".~*#")
|
||
for f in $dellist; 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
|
||
# ------------------------------------------------------------------------------
|
||
mcd()
|
||
{
|
||
if [[ ! $# -eq 1 ]]; then
|
||
disp E "Create a directory then goes inside."
|
||
disp E "Usage: mcd <directory>"
|
||
return 1
|
||
fi
|
||
mkdir -pv "$1" && cd "$1" || echo "Failed create or change directory."
|
||
}
|
||
export -f mcd
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# Rename all files in current directory to replace spaces with _
|
||
# ------------------------------------------------------------------------------
|
||
rmspc()
|
||
{
|
||
local lst=""
|
||
for opt in $@; do
|
||
case $opt in
|
||
"-h" | "--help")
|
||
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")
|
||
local recurs=1
|
||
;;
|
||
|
||
"-c"?* | "--subst-char"?*)
|
||
if [[ $(echo $opt | grep "=") ]]; then
|
||
local substchar=$(echo "$opt" | cut -f 2- -d '=')
|
||
else
|
||
local substchar='none'
|
||
fi
|
||
;;
|
||
|
||
"-v" | "--verbose")
|
||
local verb=1
|
||
;;
|
||
|
||
"-s" | "--shell")
|
||
local shell=1
|
||
;;
|
||
|
||
*)
|
||
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"
|
||
|
||
for f in *; do
|
||
[[ $recurs ]] && [[ -d "$f" ]] && (
|
||
[[ $verb ]] && disp I "Entering directory $(pwd)/$f ..."
|
||
local lastdir=$f
|
||
pushd "$f" >/dev/null
|
||
rmspc $@
|
||
popd >/dev/null
|
||
[[ $verb ]] && disp I "Leaving directory $(pwd)/$lastdir"
|
||
unset lastdir
|
||
)
|
||
|
||
if [[ $(echo $f | grep " ") ]]; then
|
||
local newf="${f// /${substchar}}"
|
||
local command="mv $mvopt \"$f\" \"$newf\""
|
||
if [[ $shell ]]; then
|
||
echo $command
|
||
else
|
||
$command
|
||
fi
|
||
fi
|
||
done
|
||
unset lst substchar verb shell newf command mvopt
|
||
}
|
||
export -f rmspc
|
||
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# display stats about a file structure
|
||
# ------------------------------------------------------------------------------
|
||
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 OPTIND opt
|
||
|
||
# Analyse options
|
||
while [[ "$1" =~ ^- ]]; do
|
||
case "$1" in
|
||
-H) human=1 ;;
|
||
-d) details=1 ;;
|
||
-m) only_avg=1; show_all=0 ;;
|
||
-M) only_med=1; show_all=0 ;;
|
||
-c) only_count=1; show_all=0 ;;
|
||
-t) only_total=1; show_all=0 ;;
|
||
-a) human=1; details=1 ;;
|
||
-x) ext_filter="${2#.}"; shift ;;
|
||
-X) ext_list="${2}"; shift ;;
|
||
--min) min_size="$2"; shift ;;
|
||
--max) max_size="$2"; shift ;;
|
||
--) shift; break ;;
|
||
-*) echo "Usage: file_stats [-h] [-d] [-mMctaxX --min N --max N] [path]"; 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" '
|
||
function human_readable(x) {
|
||
split("B KiB MiB GiB TiB", units)
|
||
for (i=1; x>=1024 && i<5; i++) x /= 1024
|
||
return sprintf("%.2f %s", x, units[i])
|
||
}
|
||
{
|
||
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 "Aucun fichier trouvé."; exit
|
||
}
|
||
|
||
moyenne = 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) {
|
||
if (human) val = human_readable(val)
|
||
printf "%-20s : %s\n", label, val
|
||
}
|
||
|
||
if (only_avg) out("Taille moyenne", moyenne)
|
||
else if (only_med) out("Taille médiane", mediane)
|
||
else if (only_count) printf "Nombre de fichiers : %d\n", count
|
||
else if (only_total) out("Taille totale", total)
|
||
else {
|
||
if (show_all || human || details) {
|
||
printf "Statistiques sur \"%s\"\n", path
|
||
printf "-------------------------\n"
|
||
}
|
||
out("Nombre de fichiers", count)
|
||
out("Taille totale", total)
|
||
out("Taille moyenne", moyenne)
|
||
out("Taille médiane", mediane)
|
||
out("Taille minimale", min)
|
||
out("Taille maximale", max)
|
||
}
|
||
|
||
if (details) {
|
||
print "\nHistogramme des tailles :"
|
||
for (i = 0; i in bucket; i++) {
|
||
low = 2^i
|
||
high = 2^(i+1)
|
||
if (i == 0)
|
||
label = sprintf("%4s – %4s", "0", "1K")
|
||
else
|
||
label = sprintf("%4s – %4s", human_readable(low), human_readable(high))
|
||
printf "%-20s : %5d fichiers\n", label, bucket[i]
|
||
}
|
||
}
|
||
}'
|
||
}
|
||
# ------------------------------------------------------------------------------
|
||
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# EOF
|