filefct bug fix
This commit is contained in:
+171
-69
@@ -37,7 +37,9 @@
|
|||||||
load_conf "filefct"
|
load_conf "filefct"
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Expand wildcards in a file/directory list and quote the results
|
# Expand wildcards in a file/directory list and quote the results: each match is
|
||||||
|
# wrapped in double quotes (with \, ", $ and ` escaped) and the matches are
|
||||||
|
# joined with the separator. Items without wildcards are quoted as they are.
|
||||||
# Usage: expandlist [options] <item1 [item2 ... itemN]>
|
# Usage: expandlist [options] <item1 [item2 ... itemN]>
|
||||||
expandlist()
|
expandlist()
|
||||||
{
|
{
|
||||||
@@ -49,9 +51,8 @@ expandlist()
|
|||||||
disp E "Invalid options, use \"expandlist --help\" to display usage."
|
disp E "Invalid options, use \"expandlist --help\" to display usage."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
local -a _pargs=()
|
# Restore the getopt-normalised (shell-quoted) argument list
|
||||||
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
|
eval set -- "$PARSED"
|
||||||
set -- "${_pargs[@]}"
|
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -60,7 +61,7 @@ expandlist()
|
|||||||
printf "Usage: expandlist [options] <item1 [item2 ... itemN]>\n\n"
|
printf "Usage: expandlist [options] <item1 [item2 ... itemN]>\n\n"
|
||||||
printf "Options:\n"
|
printf "Options:\n"
|
||||||
printf "\t-h, --help\t\tDisplay this help screen\n"
|
printf "\t-h, --help\t\tDisplay this help screen\n"
|
||||||
printf "\t-s, --separator SEP\tSet output separator (default: space)\n"
|
printf "\t-s, --separator SEP\tSet output separator (default: space); \\\\n, \\\\t... are interpreted\n"
|
||||||
printf "\t-n, --newline\t\tUse a newline as separator\n"
|
printf "\t-n, --newline\t\tUse a newline as separator\n"
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
@@ -83,15 +84,25 @@ expandlist()
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Backslash escapes in the separator (\n, \t, ...) are interpreted, so the
|
||||||
|
# configuration file can express a newline as EXPANDLIST_DEFAULT_SEPARATOR=\n
|
||||||
|
printf -v separator '%b' "$separator"
|
||||||
|
|
||||||
local item="" result="" matched=0
|
local item="" result="" matched=0
|
||||||
|
# Restore the caller's nullglob setting on exit instead of forcing it off.
|
||||||
|
local _saved_nullglob
|
||||||
|
_saved_nullglob=$(shopt -p nullglob)
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
|
# An empty IFS keeps the unquoted expansion below from splitting on spaces
|
||||||
|
# (it would cut "my dir/*.txt" in two) while globbing still happens.
|
||||||
|
local IFS=
|
||||||
|
|
||||||
for item in "$@"; do
|
for item in "$@"; do
|
||||||
local expanded=()
|
local expanded=()
|
||||||
|
|
||||||
# True glob expansion when wildcards are present.
|
# True glob expansion when wildcards are present.
|
||||||
if [[ "$item" == *'*'* || "$item" == *'?'* || "$item" == *'['* ]]; then
|
if [[ "$item" == *'*'* || "$item" == *'?'* || "$item" == *'['* ]]; then
|
||||||
# shellcheck disable=SC2206 # We actually want the word splitting
|
# shellcheck disable=SC2206 # Intentional glob expansion, no splitting (IFS is empty)
|
||||||
expanded=( $item )
|
expanded=( $item )
|
||||||
else
|
else
|
||||||
expanded=( "$item" )
|
expanded=( "$item" )
|
||||||
@@ -106,13 +117,19 @@ expandlist()
|
|||||||
if (( matched )); then
|
if (( matched )); then
|
||||||
result+="$separator"
|
result+="$separator"
|
||||||
fi
|
fi
|
||||||
|
# Escape what stays special inside double quotes so that the output
|
||||||
|
# can safely be re-evaluated (eval, command substitution, ...).
|
||||||
|
content=${content//\\/\\\\}
|
||||||
|
content=${content//\"/\\\"}
|
||||||
|
content=${content//\$/\\\$}
|
||||||
|
content=${content//\`/\\\`}
|
||||||
result+="\"$content\""
|
result+="\"$content\""
|
||||||
matched=1
|
matched=1
|
||||||
done
|
done
|
||||||
unset content
|
unset content
|
||||||
done
|
done
|
||||||
|
|
||||||
shopt -u nullglob
|
eval "$_saved_nullglob"
|
||||||
printf '%s\n' "$result"
|
printf '%s\n' "$result"
|
||||||
}
|
}
|
||||||
export -f expandlist
|
export -f expandlist
|
||||||
@@ -140,9 +157,8 @@ clean()
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local -a _pargs=()
|
# Restore the getopt-normalised (shell-quoted) argument list
|
||||||
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
|
eval set -- "$PARSED"
|
||||||
set -- "${_pargs[@]}"
|
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -190,13 +206,17 @@ clean()
|
|||||||
|
|
||||||
local dir f
|
local dir f
|
||||||
for dir in "${dirlist[@]}"; do
|
for dir in "${dirlist[@]}"; do
|
||||||
find "$dir" "${findopt[@]}" -type f \( -name "*~" -o -name "#*#" -o -name "*.bak" -o -name ".~*#" \) -print0 |
|
# "./" keeps a name starting with "-" from being read as a find option.
|
||||||
|
# -H follows a symlink given as argument (a link to a directory).
|
||||||
|
[[ $dir == -* ]] && dir="./$dir"
|
||||||
|
find -H "$dir" "${findopt[@]}" -type f \( -name "*~" -o -name "#*#" -o -name "*.bak" -o -name ".~*#" \) -print0 |
|
||||||
while IFS= read -r -d '' f; do
|
while IFS= read -r -d '' f; do
|
||||||
if (( outshell )); then
|
if (( outshell )); then
|
||||||
|
# %q prints a re-usable (shell-quoted) form of the name
|
||||||
if (( ${#rmopt[@]} )); then
|
if (( ${#rmopt[@]} )); then
|
||||||
printf 'rm %s -- "%s"\n' "${rmopt[*]}" "$f"
|
printf 'rm %s -- %q\n' "${rmopt[*]}" "$f"
|
||||||
else
|
else
|
||||||
printf 'rm -- "%s"\n' "$f"
|
printf 'rm -- %q\n' "$f"
|
||||||
fi
|
fi
|
||||||
elif (( force )); then
|
elif (( force )); then
|
||||||
# No confirmation: rm reads no stdin, so leave it attached
|
# No confirmation: rm reads no stdin, so leave it attached
|
||||||
@@ -233,11 +253,11 @@ mcd()
|
|||||||
disp E "Missing parameter. Use \"mcd --help\" to display usage."
|
disp E "Missing parameter. Use \"mcd --help\" to display usage."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if ! mkdir -pv "$1"; then
|
if ! mkdir -pv -- "$1"; then
|
||||||
disp E "Failed to create directory \"$1\"."
|
disp E "Failed to create directory \"$1\"."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
if ! cd "$1"; then
|
if ! cd -- "$1"; then
|
||||||
disp E "Failed to change to directory \"$1\"."
|
disp E "Failed to change to directory \"$1\"."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -268,9 +288,8 @@ rmspc()
|
|||||||
disp E "Invalid options, use \"rmspc --help\" to display usage."
|
disp E "Invalid options, use \"rmspc --help\" to display usage."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
local -a _pargs=()
|
# Restore the getopt-normalised (shell-quoted) argument list
|
||||||
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
|
eval set -- "$PARSED"
|
||||||
set -- "${_pargs[@]}"
|
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -280,11 +299,12 @@ rmspc()
|
|||||||
printf "Options:\n"
|
printf "Options:\n"
|
||||||
printf "\t-h, --help\t\tDisplay that help screen\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-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-c, --subst-char[=C]\tChange the replacement character (default is underscore)\n"
|
||||||
printf "\t-v, --verbose\t\tDisplay more details (recursive mode only)\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 "\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 "Note: the character must be attached (-c- or --subst-char=-). If the option\n"
|
||||||
printf " replaced with nothing (concatenation).\n"
|
printf " is given without a character, spaces are replaced with nothing\n"
|
||||||
|
printf " (concatenation). An existing file is never overwritten.\n"
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
-r|--recursive)
|
-r|--recursive)
|
||||||
@@ -315,16 +335,36 @@ rmspc()
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# getopt only attaches an optional argument written as -cX or --subst-char=X;
|
||||||
|
# "-c X" leaves X behind as a positional argument, which rmspc does not take
|
||||||
|
# (it works on the current directory) and would otherwise silently ignore.
|
||||||
|
if [[ $# -gt 0 ]]; then
|
||||||
|
disp E "Unexpected argument '$1'. The replacement character must be attached: -c$1 or --subst-char=$1"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
[[ "$substchar" == "none" ]] && substchar=""
|
[[ "$substchar" == "none" ]] && substchar=""
|
||||||
|
# A slash would turn the new name into a path
|
||||||
|
if [[ "$substchar" == */* ]]; then
|
||||||
|
disp E "The replacement character must not contain a slash."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
(( verb )) && mvopt=(-v)
|
(( verb )) && mvopt=(-v)
|
||||||
|
|
||||||
|
# Restore the caller's nullglob setting on exit instead of forcing it off.
|
||||||
|
local _saved_nullglob
|
||||||
|
_saved_nullglob=$(shopt -p nullglob)
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
local f
|
local f
|
||||||
for f in *; do
|
for f in *; do
|
||||||
if (( recurs )) && [[ -d "$f" ]]; then
|
# Symlinks to directories are not followed (no escape from the tree,
|
||||||
|
# no loops).
|
||||||
|
if (( recurs )) && [[ -d "$f" && ! -L "$f" ]]; then
|
||||||
(
|
(
|
||||||
local lastdir=$f
|
local lastdir=$f
|
||||||
(( verb )) && disp I "Entering directory $(pwd)/$f ..."
|
if (( verb )); then
|
||||||
|
disp I "Entering directory $(pwd)/$f ..."
|
||||||
|
fi
|
||||||
pushd "$f" >/dev/null || return 1
|
pushd "$f" >/dev/null || return 1
|
||||||
|
|
||||||
# Build the recursive argument list by value: the ${var:+-flag}
|
# Build the recursive argument list by value: the ${var:+-flag}
|
||||||
@@ -333,25 +373,36 @@ rmspc()
|
|||||||
local rargs=(-r)
|
local rargs=(-r)
|
||||||
(( verb )) && rargs+=(-v)
|
(( verb )) && rargs+=(-v)
|
||||||
(( shell )) && rargs+=(-s)
|
(( shell )) && rargs+=(-s)
|
||||||
(( substchar_set )) && rargs+=(-c "$substchar")
|
# Attached form, mandatory for an optional argument (see above)
|
||||||
|
(( substchar_set )) && rargs+=(--subst-char="$substchar")
|
||||||
rmspc "${rargs[@]}"
|
rmspc "${rargs[@]}"
|
||||||
|
|
||||||
popd >/dev/null || return 1
|
popd >/dev/null || return 1
|
||||||
(( verb )) && disp I "Leaving directory $(pwd)/$lastdir"
|
# "if" rather than "(( verb )) && ...": the subshell status is
|
||||||
|
# that of its last command and must stay 0 when verb is off.
|
||||||
|
if (( verb )); then
|
||||||
|
disp I "Leaving directory $(pwd)/$lastdir"
|
||||||
|
fi
|
||||||
)
|
)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$f" == *" "* ]]; then
|
if [[ "$f" == *" "* ]]; then
|
||||||
local newf="${f// /${substchar}}"
|
# Quoted replacement: an unquoted "&" would stand for the match
|
||||||
|
local newf="${f// /"$substchar"}"
|
||||||
[[ "$f" == "$newf" ]] && continue
|
[[ "$f" == "$newf" ]] && continue
|
||||||
|
# Never overwrite (mv would silently replace an existing file)
|
||||||
|
if [[ -e "$newf" || -L "$newf" ]]; then
|
||||||
|
disp E "Cannot rename \"$f\": \"$newf\" already exists."
|
||||||
|
continue
|
||||||
|
fi
|
||||||
if (( shell )); then
|
if (( shell )); then
|
||||||
if (( ${#mvopt[@]} )); then
|
if (( ${#mvopt[@]} )); then
|
||||||
printf 'mv %s -- "%s" "%s"\n' "${mvopt[*]}" "$f" "$newf"
|
printf 'mv %s -- %q %q\n' "${mvopt[*]}" "$f" "$newf"
|
||||||
else
|
else
|
||||||
printf 'mv -- "%s" "%s"\n' "$f" "$newf"
|
printf 'mv -- %q %q\n' "$f" "$newf"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
mv "${mvopt[@]}" -- "$f" "$newf" || {
|
mv -n "${mvopt[@]}" -- "$f" "$newf" || {
|
||||||
disp E "Failed renaming \"$f\" to \"$newf\"."
|
disp E "Failed renaming \"$f\" to \"$newf\"."
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -359,7 +410,7 @@ rmspc()
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
unset f
|
unset f
|
||||||
shopt -u nullglob
|
eval "$_saved_nullglob"
|
||||||
}
|
}
|
||||||
export -f rmspc
|
export -f rmspc
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@@ -369,7 +420,7 @@ export -f rmspc
|
|||||||
# Display statistics about a file tree
|
# Display statistics about a file tree
|
||||||
# Usage: file_stats [options] [path]
|
# Usage: file_stats [options] [path]
|
||||||
# Options:
|
# Options:
|
||||||
# -H, --human Human readable sizes\n"
|
# -H, --human Human readable sizes
|
||||||
# -d, --details Display details (min/max/average/median)
|
# -d, --details Display details (min/max/average/median)
|
||||||
# -m, --average Display only average size
|
# -m, --average Display only average size
|
||||||
# -M, --median Display only median size
|
# -M, --median Display only median size
|
||||||
@@ -394,9 +445,8 @@ file_stats()
|
|||||||
disp E "Invalid options, use \"file_stats --help\" to display usage."
|
disp E "Invalid options, use \"file_stats --help\" to display usage."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
local -a _pargs=()
|
# Restore the getopt-normalised (shell-quoted) argument list
|
||||||
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
|
eval set -- "$PARSED"
|
||||||
set -- "${_pargs[@]}"
|
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -476,16 +526,23 @@ file_stats()
|
|||||||
done
|
done
|
||||||
|
|
||||||
[[ -n "$1" ]] && path="$1"
|
[[ -n "$1" ]] && path="$1"
|
||||||
|
if [[ ! -e "$path" ]]; then
|
||||||
|
disp E "file_stats: no such file or directory: ${path}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
# "./" keeps a name starting with "-" from being read as a find option
|
||||||
|
[[ $path == -* ]] && path="./$path"
|
||||||
|
|
||||||
# Prepare find filters
|
# Prepare find filters
|
||||||
local find_cmd=(find "$path" -type f)
|
local find_cmd=(find "$path" -type f)
|
||||||
|
|
||||||
# Single extension filter
|
# Single extension filter (case-insensitive). When combined with
|
||||||
|
# --ext-list, a file must satisfy both, as find ANDs the tests.
|
||||||
if [[ -n "$ext_filter" ]]; then
|
if [[ -n "$ext_filter" ]]; then
|
||||||
find_cmd+=(-iname "*.$ext_filter")
|
find_cmd+=(-iname "*.$ext_filter")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Extension list filter
|
# Extension list filter: ( -iname *.a -o -iname *.b ... )
|
||||||
local exts
|
local exts
|
||||||
if [[ -n "$ext_list" ]]; then
|
if [[ -n "$ext_list" ]]; then
|
||||||
IFS=',' read -ra exts <<< "$ext_list"
|
IFS=',' read -ra exts <<< "$ext_list"
|
||||||
@@ -507,20 +564,35 @@ file_stats()
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
local bytes
|
||||||
if [[ -n "$min_size" ]]; then
|
if [[ -n "$min_size" ]]; then
|
||||||
find_cmd+=(-size +"$(numfmt --from=iec "$min_size")"c)
|
bytes=$(numfmt --from=iec "$min_size" 2>/dev/null) || {
|
||||||
|
disp E "file_stats: invalid --min size: ${min_size}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
# find's "-size +N" means "strictly more than N bytes"; the minimum
|
||||||
|
# is inclusive, hence N-1 (nothing to filter for a minimum of 0).
|
||||||
|
(( bytes > 0 )) && find_cmd+=(-size +"$(( bytes - 1 ))"c)
|
||||||
fi
|
fi
|
||||||
if [[ -n "$max_size" ]]; then
|
if [[ -n "$max_size" ]]; then
|
||||||
find_cmd+=(-size -"$(( $(numfmt --from=iec "$max_size") + 1 ))"c)
|
bytes=$(numfmt --from=iec "$max_size" 2>/dev/null) || {
|
||||||
|
disp E "file_stats: invalid --max size: ${max_size}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
# "-size -N" means "less than N bytes": N+1 makes the maximum inclusive.
|
||||||
|
find_cmd+=(-size -"$(( bytes + 1 ))"c)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Execution
|
# Execution: one size per line, sorted ascending (the median needs it),
|
||||||
|
# then summarised by awk. The path goes through ENVIRON, as awk -v would
|
||||||
|
# interpret backslashes in it.
|
||||||
"${find_cmd[@]}" -printf "%s\n" 2>/dev/null | sort -n | \
|
"${find_cmd[@]}" -printf "%s\n" 2>/dev/null | sort -n | \
|
||||||
|
FILE_STATS_PATH="$path" \
|
||||||
awk -v human="$human" -v details="$details" -v only_avg="$only_avg" \
|
awk -v human="$human" -v details="$details" -v only_avg="$only_avg" \
|
||||||
-v only_med="$only_med" -v only_count="$only_count" \
|
-v only_med="$only_med" -v only_count="$only_count" \
|
||||||
-v only_total="$only_total" -v show_all="$show_all" -v path="$path" '
|
-v only_total="$only_total" -v show_all="$show_all" '
|
||||||
# Convert function
|
# Convert function (i and units are local: extra parameters)
|
||||||
function human_readable(x) {
|
function human_readable(x, i, units) {
|
||||||
split("B KiB MiB GiB TiB", units)
|
split("B KiB MiB GiB TiB", units)
|
||||||
i = 1
|
i = 1
|
||||||
while (x >= 1024 && i < 5) {
|
while (x >= 1024 && i < 5) {
|
||||||
@@ -541,14 +613,17 @@ file_stats()
|
|||||||
total += $1
|
total += $1
|
||||||
if (min == "" || $1 < min) min = $1
|
if (min == "" || $1 < min) min = $1
|
||||||
if (max == "" || $1 > max) max = $1
|
if (max == "" || $1 > max) max = $1
|
||||||
if ($1 == 0) bucket[0]++
|
# Histogram bucket b holds sizes in [1024^b, 1024^(b+1)); bucket 0
|
||||||
else {
|
# also takes empty files. Computed with an integer loop: a
|
||||||
b = int(log($1)/log(1024))
|
# log() ratio can fall just below an exact power of 1024.
|
||||||
bucket[b]++
|
v = $1; b = 0
|
||||||
}
|
while (v >= 1024) { v = int(v / 1024); b++ }
|
||||||
|
bucket[b]++
|
||||||
|
if (b > maxb) maxb = b
|
||||||
}
|
}
|
||||||
|
|
||||||
END {
|
END {
|
||||||
|
path = ENVIRON["FILE_STATS_PATH"]
|
||||||
count = NR
|
count = NR
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
print "No files found."
|
print "No files found."
|
||||||
@@ -584,8 +659,10 @@ file_stats()
|
|||||||
if (details) {
|
if (details) {
|
||||||
print "\nSize histogram:"
|
print "\nSize histogram:"
|
||||||
|
|
||||||
# Use a separate array for the loop to avoid collision
|
# Walk the buckets in ascending order (awk "for (b in array)" gives
|
||||||
for (b in bucket) {
|
# no defined order); empty buckets are skipped.
|
||||||
|
for (b = 0; b <= maxb; b++) {
|
||||||
|
if (!(b in bucket)) continue
|
||||||
# Pre-calculate label parts
|
# Pre-calculate label parts
|
||||||
# 1024^0 = 1 (B), 1024^1 = 1K, etc.
|
# 1024^0 = 1 (B), 1024^1 = 1K, etc.
|
||||||
low = (b == 0) ? 0 : (1024^b)
|
low = (b == 0) ? 0 : (1024^b)
|
||||||
@@ -595,7 +672,6 @@ file_stats()
|
|||||||
(b == 0) ? "0" : human_readable(low),
|
(b == 0) ? "0" : human_readable(low),
|
||||||
human_readable(high))
|
human_readable(high))
|
||||||
|
|
||||||
# We store buckets in an array, access them by index b
|
|
||||||
printf "%-25s : %6d files\n", label, bucket[b]
|
printf "%-25s : %6d files\n", label, bucket[b]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -624,9 +700,8 @@ findbig()
|
|||||||
disp E "Invalid options, use \"findbig --help\" to display usage."
|
disp E "Invalid options, use \"findbig --help\" to display usage."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
local -a _pargs=()
|
# Restore the getopt-normalised (shell-quoted) argument list
|
||||||
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
|
eval set -- "$PARSED"
|
||||||
set -- "${_pargs[@]}"
|
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -646,10 +721,6 @@ findbig()
|
|||||||
;;
|
;;
|
||||||
-l|--limit)
|
-l|--limit)
|
||||||
limit="$2"
|
limit="$2"
|
||||||
[[ "$limit" =~ ^[0-9]+$ ]] || {
|
|
||||||
disp E "Invalid limit: must be a positive integer."
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
-x|--one-fs)
|
-x|--one-fs)
|
||||||
@@ -667,7 +738,16 @@ findbig()
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Checked here so that the configured default (FINDBIG_DEFAULT_LIMIT) is
|
||||||
|
# validated as well as -l.
|
||||||
|
if [[ ! "$limit" =~ ^[1-9][0-9]*$ ]]; then
|
||||||
|
disp E "Invalid limit '${limit}': must be a positive integer."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
local dir="${1:-.}"
|
local dir="${1:-.}"
|
||||||
|
# "./" keeps a name starting with "-" from being read as a find option
|
||||||
|
[[ $dir == -* ]] && dir="./$dir"
|
||||||
|
|
||||||
# Prepare find arguments in an array for cleaner handling
|
# Prepare find arguments in an array for cleaner handling
|
||||||
local find_args=(-L "$dir")
|
local find_args=(-L "$dir")
|
||||||
@@ -731,9 +811,8 @@ findzero()
|
|||||||
disp E "Invalid options, use \"findzero --help\" to display usage."
|
disp E "Invalid options, use \"findzero --help\" to display usage."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
local -a _pargs=()
|
# Restore the getopt-normalised (shell-quoted) argument list
|
||||||
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
|
eval set -- "$PARSED"
|
||||||
set -- "${_pargs[@]}"
|
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -770,7 +849,18 @@ findzero()
|
|||||||
done
|
done
|
||||||
|
|
||||||
local dir="${1:-.}"
|
local dir="${1:-.}"
|
||||||
local find_args=("-L" "$dir" "-type" "f" "-empty")
|
# "./" keeps a name starting with "-" from being read as a find option
|
||||||
|
[[ $dir == -* ]] && dir="./$dir"
|
||||||
|
|
||||||
|
# Symlinks are followed to report/count empty targets, but never when
|
||||||
|
# deleting: with -L, a link to an empty file matches and "delete" would
|
||||||
|
# remove the link itself.
|
||||||
|
local follow=1
|
||||||
|
(( delete )) && follow=0
|
||||||
|
|
||||||
|
local find_args=()
|
||||||
|
(( follow )) && find_args+=("-L")
|
||||||
|
find_args+=("$dir" "-type" "f" "-empty")
|
||||||
|
|
||||||
(( one_fs )) && find_args+=("-xdev")
|
(( one_fs )) && find_args+=("-xdev")
|
||||||
|
|
||||||
@@ -779,13 +869,19 @@ findzero()
|
|||||||
_fd=$(command -v fd 2>/dev/null || command -v fdfind 2>/dev/null)
|
_fd=$(command -v fd 2>/dev/null || command -v fdfind 2>/dev/null)
|
||||||
|
|
||||||
if [[ -n "$_fd" ]]; then
|
if [[ -n "$_fd" ]]; then
|
||||||
local _fd_args=(--follow --no-ignore --hidden --absolute-path -t f --size -1b)
|
local _fd_args=(--no-ignore --hidden --absolute-path -t f --size -1b)
|
||||||
|
(( follow )) && _fd_args+=(--follow)
|
||||||
(( one_fs )) && _fd_args+=(--one-file-system)
|
(( one_fs )) && _fd_args+=(--one-file-system)
|
||||||
_fd_args+=(. "$dir")
|
_fd_args+=(. "$dir")
|
||||||
|
# fd's "--size -1b" means "at most 1 byte", so it also lists files of
|
||||||
|
# exactly one byte. Every result is therefore re-checked with [[ -s ]]
|
||||||
|
# (true when the size is not zero) and skipped if not empty: deleting a
|
||||||
|
# one-byte file would be data loss.
|
||||||
if (( delete )); then
|
if (( delete )); then
|
||||||
disp W "Deleting empty files in $dir..."
|
disp W "Deleting empty files in $dir..."
|
||||||
local f
|
local f
|
||||||
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
|
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
|
||||||
|
[[ -s "$f" ]] && continue
|
||||||
printf "%s\n" "$f"
|
printf "%s\n" "$f"
|
||||||
rm -f -- "$f"
|
rm -f -- "$f"
|
||||||
done
|
done
|
||||||
@@ -793,11 +889,16 @@ findzero()
|
|||||||
elif (( details )); then
|
elif (( details )); then
|
||||||
local f
|
local f
|
||||||
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
|
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
|
||||||
|
[[ -s "$f" ]] && continue
|
||||||
ls -ls -- "$f"
|
ls -ls -- "$f"
|
||||||
done
|
done
|
||||||
unset f
|
unset f
|
||||||
else
|
else
|
||||||
"$_fd" "${_fd_args[@]}" 2>/dev/null
|
local f
|
||||||
|
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
|
||||||
|
if [[ ! -s "$f" ]]; then printf "%s\n" "$f"; fi
|
||||||
|
done
|
||||||
|
unset f
|
||||||
fi
|
fi
|
||||||
elif (( delete )); then
|
elif (( delete )); then
|
||||||
disp W "Deleting empty files in $dir..."
|
disp W "Deleting empty files in $dir..."
|
||||||
@@ -831,9 +932,8 @@ finddead()
|
|||||||
disp E "Invalid options, use \"finddead --help\" to display usage."
|
disp E "Invalid options, use \"finddead --help\" to display usage."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
local -a _pargs=()
|
# Restore the getopt-normalised (shell-quoted) argument list
|
||||||
readarray -td '' _pargs < <(printf '%s' "$PARSED" | xargs printf '%s\0')
|
eval set -- "$PARSED"
|
||||||
set -- "${_pargs[@]}"
|
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -870,6 +970,8 @@ finddead()
|
|||||||
done
|
done
|
||||||
|
|
||||||
local dir="${1:-.}"
|
local dir="${1:-.}"
|
||||||
|
# "./" keeps a name starting with "-" from being read as a find option
|
||||||
|
[[ $dir == -* ]] && dir="./$dir"
|
||||||
# -xtype l searches for links that do not point to an existing file
|
# -xtype l searches for links that do not point to an existing file
|
||||||
local find_args=("$dir" "-xtype" "l")
|
local find_args=("$dir" "-xtype" "l")
|
||||||
(( one_fs )) && find_args+=("-xdev")
|
(( one_fs )) && find_args+=("-xdev")
|
||||||
@@ -887,19 +989,19 @@ finddead()
|
|||||||
disp W "Deleting dead symlinks in $dir..."
|
disp W "Deleting dead symlinks in $dir..."
|
||||||
local f
|
local f
|
||||||
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
|
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
|
||||||
[[ -e "$f" ]] || { printf "%s\n" "$f"; rm -f -- "$f"; }
|
if [[ ! -e "$f" ]]; then printf "%s\n" "$f"; rm -f -- "$f"; fi
|
||||||
done
|
done
|
||||||
unset f
|
unset f
|
||||||
elif (( details )); then
|
elif (( details )); then
|
||||||
local f
|
local f
|
||||||
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
|
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
|
||||||
[[ -e "$f" ]] || ls -ls -- "$f"
|
if [[ ! -e "$f" ]]; then ls -ls -- "$f"; fi
|
||||||
done
|
done
|
||||||
unset f
|
unset f
|
||||||
else
|
else
|
||||||
local f
|
local f
|
||||||
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
|
"$_fd" "${_fd_args[@]}" 2>/dev/null | while IFS= read -r f; do
|
||||||
[[ -e "$f" ]] || printf "%s\n" "$f"
|
if [[ ! -e "$f" ]]; then printf "%s\n" "$f"; fi
|
||||||
done
|
done
|
||||||
unset f
|
unset f
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user