1
0
angie-conv-image/scripts/overlay-dir-list.sh
2024-07-11 13:37:35 +03:00

279 lines
5.3 KiB
Bash
Executable File

#!/bin/sh
set -f
## ephemeral limit, keep in sync with verify_depth()
max_depth=99
default_depth=20
me="${0##*/}"
usage() {
cat >&2 <<-EOF
# usage: ${me} [options] <directory> [<directory> ..]
# options:
# -d <N>, --depth <N>, --depth=<N>
# limit search depth to <N> (default: ${default_depth}, max: ${max_depth})
# -z, --zero
# separate entries with NUL instead of LF
# -s, --split-path
# separate directory and file name parts with "|" instead of "/"
EOF
exit "${1:-0}"
}
[ $# != 0 ] || usage
msg() { echo ${1:+"# ${me}: $*"} >&2 ; }
msgf() {
_____fmt="$1" ; shift
env printf "# ${me}: ${_____fmt}\\n" ${@+"$@"} >&2
unset _____fmt
}
verify_depth() {
case "$1" in
[1-9] | [1-9][0-9] ) return 0 ;;
esac
msgf 'error: wrong depth specifier: %q' "$1"
usage 1
}
o_depth=
f_zero_eol=
f_split_path=
## process options
want_value=
n_opt=0
for i ; do
if [ -n "${want_value}" ] ; then
case "${want_value}" in
depth )
o_depth="$i"
verify_depth "${o_depth}"
;;
esac
want_value=
n_opt=$((n_opt+1))
continue
fi
case "$i" in
-d | --depth | --depth=* )
if [ -n "${o_depth}" ] ; then
msg 'error: "depth" option already set'
usage 1
fi
case "$i" in
*=* )
o_depth="${i#*=}"
verify_depth "${o_depth}"
;;
* ) want_value=depth ;;
esac
;;
-z | --zero )
if [ -n "${f_zero_eol}" ] ; then
msg 'error: "zero" flag already set'
usage 1
fi
f_zero_eol=1
;;
-s | --split-path )
if [ -n "${f_split_path}" ] ; then
msg 'error: "split-path" flag already set'
usage 1
fi
f_split_path=1
;;
-* )
msgf 'unknown option: %q' "$i"
usage 1
;;
* ) break ;;
esac
n_opt=$((n_opt+1))
done
[ ${n_opt} = 0 ] || shift ${n_opt}
[ $# != 0 ] || usage 1
: "${o_depth:=${default_depth}}"
path_sep='/'
[ -z "${f_split_path}" ] || path_sep='|'
entry_sep='\n'
[ -z "${f_zero_eol}" ] || entry_sep='\0'
entry_fmt="%s${path_sep}%s${entry_sep}"
## work directory
w=$(mktemp -d) ; : "${w:?}"
_cleanup() {
cd /
rm -rf -- "$w"
}
adjust_with_skiplist() {
[ -s "$1" ] || return 0
[ -s "$2" ] || return 0
__skiptype="$3"
while read -r __skipname ; do
[ -n "${__skipname}" ] || continue
mawk \
-v "n=${__skipname}" \
-v "t=${__skiptype}" \
'
BEGIN {
RS = ORS = "\0";
FS = OFS = "|";
}
{
if ($3 != n) { print; next; }
if (t == "") { next; }
if ($1 ~ t) { next; }
print;
}
' < "$2" > "$2.t"
mv -f "$2.t" "$2"
done < "$1"
unset __skipname __skiptype
}
: > "$w/all"
## process arguments
for i ; do
## arguments with '|' are skipped - try naming paths simplier
case "$i" in
*\|* )
msgf 'argument with "|" is IGNORED: %q' "$i"
continue
;;
esac
## non-existent directories are silently skipped
[ -d "$i" ] || continue
## generate directory listing
## paths with '\n' and '|' are silently skipped - try naming paths simplier
find "$i/" -follow -mindepth 1 -maxdepth 1 ! -name '*|*' -printf '%y|%h|%P\0' \
| sed -zE '/\n/d' > "$w/current"
## filter out uncommon entry types
## allowed types:
## d - directory
## f - file
## less common types (but also allowed):
## b - block device
## c - character device
## p - pipe
## s - socket
sed -i -zE '/^[^bcdfps]/d' "$w/current"
## empty directory - continue with next argument
[ -s "$w/current" ] || continue
## generate skiplist
grep -zE '\.-$' < "$w/current" \
| cut -z -d '|' -f 3 \
| sed -zE 's/\.-$//' \
| tr '\0' '\n' > "$w/skip"
## adjust current list: remove entries from skiplist
sed -i -zE '/\.-$/d' "$w/current"
## adjust accumulated list: remove "skip" entries
adjust_with_skiplist "$w/skip" "$w/all"
rm -f "$w/skip"
## adjust accumulated list: override all entries with "non-dir" entries from current list
## NB: entry type from current list overrides entry from previously accumulated list
sed -zEn '/^[^d]/p' < "$w/current" \
| cut -z -d '|' -f 3 \
| tr '\0' '\n' > "$w/nondir"
adjust_with_skiplist "$w/nondir" "$w/all"
rm -f "$w/nondir"
## adjust accumulated list: override "non-dir" entries with "dir" entries from current list
## NB: entry type from current list overrides entry from previously accumulated list
sed -zEn '/^d/p' < "$w/current" \
| cut -z -d '|' -f 3 \
| tr '\0' '\n' > "$w/dir"
adjust_with_skiplist "$w/dir" "$w/all" '[^d]'
rm -f "$w/dir"
## merge lists
sort -zV -t '|' -k 3 < "$w/current" >> "$w/all"
rm -f "$w/current"
done
# nothing to do?
if ! [ -s "$w/all" ] ; then
_cleanup
exit 0
fi
cut -z -d '|' -f '3' < "$w/all" \
| sort -zuV \
| tr '\0' '\n' > "$w/names"
: > "$w/dirnames"
sub_depth=$(( o_depth - 1 ))
while read -r name ; do
mawk \
-v "n=${name}" \
'
BEGIN {
RS = "\0"; ORS = "\n";
FS = OFS = "|";
}
$3 == n { print; }
' < "$w/all" > "$w/list"
while IFS='|' read -r dtype dir _name ; do
[ -n "${dtype}" ] || continue
# ${_name} is unused
case "${dtype}" in
d )
[ "${sub_depth}" != 0 ] || continue
if grep -Fxq -e "${name}" "$w/dirnames" ; then
continue
fi
printf '%s\n' "${name}" >> "$w/dirnames"
mawk \
-v "n=${name}" \
'
BEGIN {
ORS = "\0";
FS = "|"; OFS = "/";
}
$3 == n { print $2,$3; }
' < "$w/list" > "$w/dir.args"
xargs -0 -a "$w/dir.args" "$0" ${f_zero_eol:+-z} ${f_split_path:+-s} -d "${sub_depth}"
rm -f "$w/dir.args"
;;
* )
printf "${entry_fmt}" "${dir}" "${name}"
;;
esac
done < "$w/list"
done < "$w/names"
_cleanup
exit 0