From 168d234cfd27016de7e7353ee8655ada18352ec7 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Tue, 24 Jan 2023 12:44:52 -0700 Subject: hstow: use find(1)'s -path argument rather than filtering its output The -path argument is included in POSIX.1-2017. Also improve the awk code in the case where .hstow-always-adopt doesn't exist. --- bin/hstow | 88 ++++++++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 29 deletions(-) (limited to 'bin/hstow') diff --git a/bin/hstow b/bin/hstow index 7dbcf0ec..9e2e42c2 100755 --- a/bin/hstow +++ b/bin/hstow @@ -78,11 +78,37 @@ readlinks () { } disjoin_file () { - if [ -e "$DIR/$1" ]; then - while read -r line; do - [ -n "$line" ] && printf "|$2" "$line" - done <"$DIR/$1" | sed 's#^.##; s#/#\\/#g' - fi + while read -r line; do + [ -n "$line" ] && printf "|$2" "$line" + done <"$DIR/$1" | sed 's#^.##; s#/#\\/#g' +} + +globs_to_find_args () { + local file="$DIR/$1"; shift + printf '%s\n' "$@" | cat - $([ -e "$file" ] && echo "$file") \ + | awk -F'\n' -vOFS='\t' \ + '/\/\*$/ { sub(/..$/, ""); prune[++c] = "./" $0; next } + $0 { notpath[++d] = "./" $0 } + # We want to prevent find(1) recursing into directories + # of these names, but not prevent the remainder of the + # find(1) expression from matching the names themselves. + # This is what is correct for globs of the form "dir/*". + # While it is true that neither of the find(1) commands + # which use globs_to_find_args() match directories, such + # that it would not make sense to add a line to one of + # .hstow-*-ignore with the aim of excluding the contents + # of a directory but not the directory itself, it could + # be that the name is currently a symlink that we should + # unstow. (Also note that we do not pass -L to find(1), + # so we will not recurse into symlinks to dirs anyway.) + END { if (c) { + print "(", "(", "-path", prune[1] + for (i = 2; i <= c; i++) + print "-o", "-path", prune[i] + print ")", "-prune", "-o", "-name", "*", ")" + } + for (j in notpath) print "!", "-path", notpath[j] + }' } usage () { @@ -104,22 +130,24 @@ stow () { } stow1 () { - ignores="$(disjoin_file .hstow-local-ignore "./%s")" - - # Files that (i) always/often have their symlinks replaced with - # regular files when applications access them; and (ii) we don't - # ever want to edit the copy under $DIR directly, but only via the - # link/copy under $HOME. - $always_adopt \ - && adoptp=1 \ - || adoptp="rel ~ /^($(disjoin_file .hstow-always-adopt "%s"))/" - - find . ! -name . ! -type d ! -name "$cchars" \ - ! -name .gitignore \ - ! -name .hstow-local-ignore \ - ! -name .hstow-always-adopt \ - ! -name .hstow-unstow-ignore \ - | awk -F'\n' -vOFS='\t' '! /^(\.\/\.git\/|'"$ignores"')/ \ + if $always_adopt; then + adoptp=1 + elif ! [ -e .hstow-always-adopt ]; then + adoptp=0 + else + # EREs matching files that (i) always/often have their symlinks + # replaced with regular files when applications access them; and + # (ii) we don't ever want to edit the copy under $DIR directly, + # but only via the link/copy under $HOME. + # We might list globs in this file & convert them to EREs here. + adoptp="$(printf 'rel ~ /^(%s)/' \ + "$(disjoin_file .hstow-always-adopt "%s")")" + fi + find . $(globs_to_find_args .hstow-local-ignore ".git/*") \ + ! -name . ! -type d ! -name "$cchars" ! -name .gitignore \ + ! -name .hstow-local-ignore ! -name .hstow-always-adopt \ + ! -name .hstow-unstow-ignore -print \ + | awk -F'\n' -vOFS='\t' ' { rel = $1; gsub(/\/dot[-.]/, "/.", rel); gsub(/^\.\//, "", rel) dotdotslashes = rel sub(/[^\/]*$/, "", dotdotslashes) @@ -157,11 +185,9 @@ stow1 () { unstow () { cd "$HOME" - # For speed, skip directories into which we'll never stow anything. - ignores="$(disjoin_file .hstow-unstow-ignore "./%s")" - - dir_pat=".$(echo $DIR | cut -c$(echo $HOME | wc -m | tr -d ' ')-)/" - dirs_pat="$(echo "^($dir_pat|$ignores)" | sed -e 's#\.#\\.#g')" + # .hstow-unstow-ignore is a list of globs matching dirs into which + # we'll never stow anything. We have this for the sake of speed. + # # awk's close() calls pclose(3), completing all the link deletions. # POSIX.1 "Utility Description Defaults", "Consequences of Errors" # implies that should rmdir(1) encounter a non-empty directory, it @@ -169,9 +195,13 @@ unstow () { # Thus, here, -p means that we do not need to sort the operands. # We don't know the code with which rmdir(1) will exit, and if it is # 255 then xargs will give up. So we wrap in a call to sh -c. - find . ! \( -user "$(id -un)" -o -group "$(id -gn)" \) \ - -prune -o -type l ! -name . ! -name "$cchars" -print \ - | grep -Ev "$dirs_pat" | readlinks 0 true \ + find . ! \( -user "$(id -un)" -o -group "$(id -gn)" \) -prune -o \ + $(globs_to_find_args \ + .hstow-unstow-ignore \ + "$(echo "$DIR" \ + | cut -c$((1 + $(echo "$HOME" | wc -m)))-)/*") \ + -type l ! -name . ! -name "$cchars" -print \ + | readlinks 0 true \ | awk -F'\t' -vOFS='\t' '$2 ~ /^(\.\.\/)*\.STOW\/'"$NAME"'\// \ { gsub(/"/, "\"'"'"'\"'"'"'\"", $1) printf "\"%s\"\n", $1 | "xargs -E '' -- rm -f" -- cgit v1.2.3