I wrote the following script to interactively and recursively remove orphan backup files, i.e. remove each file.txt~ that does not have a corresponding file.txt.
#!/bin/sh -x
set -o errexit
unalias -a
backups=$(find . -name "*~")
orphans=""
while read -r file
do
[ ! -e "${file%~}" ] && orphans=$(echo "$file\n$orphans");
done << EOF
$backups
EOF
if [ -z "$orphans" ]; then
echo "No orphans."
else
echo "orphans:\n$orphans"
echo "$orphans" | xargs --interactive -d '\n' rm
fi
This script does very weird things in a random fashion. Sometimes it behaves correctly, sometimes it ignores the -x options passed to sh, sometimes it executes commented code, sometimes the tests give just wrong results.
The problem seems to be related to the here script, since by redirecting the output of find to a temporary file all problems seem to disappear. But why, where is the error?
Solution (thanks to Dennis Williamson): Escape the ~ character. The unescaped ~ in the parameter expansion ${file%~} was creating somehow all the unpredictable behavior.
A more readable and deterministic solution, with some fat cut out, could be (thanks to the suggestions of Mikel):
#!/bin/sh
IFS='
'
for backup in $(find . -type f -name "*~"); do
if [ ! -e "${backup%\~}" ]; then
rm -i "$backup"
fi
done
If you are a while read loop fan, things get less elegant because the interactive command rm -i can not be used (it will conflict with the read command). Anyway, a solution could be:
#!/bin/sh
orphans=""
while read -r backup; do
if [ ! -e "${backup%\~}" ]; then
orphans=$(echo "$backup\n$orphans");
fi
done << EOF
$(find . -type f -name "*~")
EOF
if [ ! -z "$orphans" ]; then
echo "$orphans" | xargs --interactive -d '\n' rm
fi
Or, a more complex way is also suggested by Dennis Williamson.