Византийское поведение в сценарии оболочки

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

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


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


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

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

Если Вы - a 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

Или, более сложный путь также предлагается Dennis Williamson.

3
задан 20.03.2017, 12:17

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 ...)

...
2
ответ дан 08.12.2019, 01:06

Что Вы имеете в виду "иногда"? Иногда на том же поле? Или другое поведение в различных системах?

То, что Вы имеете в виду, "выполняет прокомментированный код"? Ваш пример не имеет никаких комментариев.

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

  • попробовать set -x вместо /bin/sh -x
  • лучше использовать set -e чем set -o errexit
  • при использовании удара то звоните /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
1
ответ дан 08.12.2019, 01:06

Теги

Похожие вопросы