3

Я написал следующий скрипт для интерактивного и рекурсивного удаления потерянных файлов резервных копий, т. Е. Удаления каждого file.txt~ , который не имеет соответствующего 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

Этот сценарий делает очень странные вещи случайным образом. Иногда он ведет себя правильно, иногда он игнорирует параметры -x, переданные sh, иногда он выполняет закомментированный код, иногда тесты дают просто неправильные результаты.

Кажется, что проблема связана со скриптом here, так как при перенаправлении вывода find во временный файл все проблемы исчезают. Но почему, где ошибка?


Решение (благодаря Dennis Williamson): Побег ~ характер. Неэкранированный ~ в расширении параметра ${file%~} каким-то образом создавал непредсказуемое поведение.


Более читаемое и детерминированное решение, с небольшим количеством жира, может быть (благодаря предложениям Микеля):

#!/bin/sh
IFS='
'
for backup in $(find . -type f -name "*~"); do
    if [ ! -e "${backup%\~}" ]; then
        rm -i "$backup"
    fi
done

Если вы являетесь поклонником цикла while read , все становится менее изящным, потому что интерактивная команда rm -i не может использоваться (она будет конфликтовать с командой read ). В любом случае, решение может быть:

#!/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

Или Деннис Уильямсон также предлагает более сложный способ.

2 ответа2

2

Вероятно, вам нужно избежать тильды в расширении фигурных скобок, иначе оно будет расширено до вашего домашнего каталога.

Почему не конвейер find в ваше while цикла вместо того , чтобы создавать переменные , чтобы держать их? Внутри вашего цикла просто выполните rm -i "$file" .

#!/bin/sh -x

set -o errexit
unalias -a

exec 3<&0    # open a duplicate of stdin
flag=false
find . -name "*~" | while IFS=$'\n' read -r file
do
    if [ ! -e "${file%\~}" ]
    then
        orphans="$file"$'\n'"$orphans"

        # use an alternate file descriptor so read and rm -i get along
        rm -i "$file" <&3
        flag=true
    fi
done
exec 3<&-    # close the file descriptor

if ! $flag
then
    echo "No orphans."
else
    echo "orphans:\n$orphans"
fi

Если вы хотите использовать Bash, вам нужно переместить find в конец цикла, чтобы подоболочка не создавалась.

#!/bin/bash

...

# use an alternate file descriptor so read and rm -i get along
while read -u 3 -r ...

    rm -i ...
    ...

done 3< <(find ...)

...
1

Что вы имеете в виду "иногда"? Иногда на одной коробке? Или разное поведение в разных системах?

Что вы имеете в виду "выполняет закомментированный код"? Ваш пример не имеет комментариев.

Некоторые мысли:

  • попробуйте set -x вместо /bin/sh -x
  • лучше использовать set -e чем set -o errexit
  • если вы используете bash, тогда вызовите /bin/bash , а не /bin/sh , что может быть чем-то другим
  • эхо не нужно, просто используйте = с буквальным переводом строки
  • если вам нужно использовать echo , вы должны использовать echo -e или убедиться, что xpg_echo установлен
  • ваша линия сирот ставит их в обратном порядке, это преднамеренно?
  • Ваш цикл чтения завершится ошибкой, если имена файлов содержат пробелы, вы должны сначала установить IFS

Более простая версия:

#!/bin/bash

set -x
set -e

IFS=$'\n'
orphans=false
for backup in $(find . -type f -name "*~"); do
    original=${backup%\~}
    if [ ! -e "$original" ]; then
        orphans=true
        rm -i "$backup"
    fi  
done

if ! $orphans; then
    echo "No orphans."
fi

Или, если вы хотите, чтобы он работал, используя /bin/sh:

#!/bin/sh

set -x
set -e

IFS='
'
orphans=false
for backup in $(find . -type f -name "*~"); do
    original=${backup%\~}
    if [ ! -e "$original" ]; then
        orphans=true
        rm -i "$backup"
    fi  
done

if ! $orphans; then
    echo "No orphans."
fi

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .