рм работает в командной строке, но не в сценарии

11

Когда я делаю это rm *.old.*в командной строке, он удаляется правильно, но когда я делаю это в следующей части моего скрипта, он не удаляет все *.old.*файлы.

Что не так в моем скрипте bash:

 for i in ./*; do
    if [[ -f $i ]];  then

        if [[ $i  ==  *.old.* ]]; then
                oldfile=$i
                echo "this file is to be removed: $oldfile"
                rm $oldfile
                exec 2>errorfile
            if [ -s $errorfile ]
            then
                echo "rm failed"
            else
                echo "removed $oldfile!"
            fi
        else
            echo "file with old extension  does not exist"
        fi

        orig=$i
        dest=$i.old
        cp $orig $dest
        echo "Copied $i"

    else
        echo "${i} is not a file"
    fi 
done
дон
источник

Ответы:

4

Если я понимаю, что вы делаете (удалите все файлы с .oldсуффиксом и скопируйте все существующие файлы с .oldсуффиксом), вы можете использовать вместо этого поиск:

#!/bin/sh

find . -maxdepth 1 -name \*.old -type f -printf "deleting %P\n" -delete
find . -maxdepth 1 -type f -printf "copying %P to %P.old\n" -exec cp '{}' '{}.old' \;

-maxdepth 0останавливает поиск команды в подкаталогах, -type fищет только обычные файлы; -printfсоздает сообщения ( %Pэто имя файла найдено). -exec cpВызывает функцию копирования и '{}'имя файла

Ник Силлито
источник
14

В вашем скрипте есть различные возможные точки отказа. Прежде всего, rm *.old*будем использовать глобирование для создания списка всех подходящих файлов, которые могут иметь дело с именами файлов, содержащими пробелы. Однако ваш сценарий назначает переменную каждому результату глобуса и делает это без кавычек. Это сломается, если ваши имена файлов содержат пробелы. Например:

$ ls
'file name with spaces.old.txt'  file.old.txt
$ rm *.old.*   ## works: both files are deleted

$ touch "file.old.txt" "file name with spaces.old.txt"
$ for i in ./*; do oldfile=$i; rm -v $oldfile; done
rm: cannot remove './file': No such file or directory
rm: cannot remove 'name': No such file or directory
rm: cannot remove 'with': No such file or directory
rm: cannot remove 'spaces.old.txt': No such file or directory
removed './file.old.txt'

Как видите, цикл не удался для файла с пробелами в его имени. Чтобы сделать это правильно, вам нужно заключить переменную в кавычки:

$ for i in ./*; do oldfile="$i"; rm -v "$oldfile"; done
removed './file name with spaces.old.txt'
removed './file.old.txt'

Та же проблема касается почти каждого использования $iв вашем скрипте. Вы должны всегда указывать свои переменные .

Следующая возможная проблема заключается в том, что вы, похоже, ожидаете, что *.old.*файлы совпадут с расширением .old. Это не так. Он соответствует «0 или более символов» ( *), затем a ., затем «old», затем другому, .а затем «0 или более символов снова». Это означает, что он не будет совпадать с чем-то вроде file.old, а только с чем-то вроде `file.old.foo:

$ ls
file.old  file.old.foo
$ for i in *; do if [[ "$i" == *.old.* ]]; then echo $i; fi; done
file.old.foo     

Таким образом, противник не совпадает file.old. В любом случае ваш сценарий намного сложнее, чем нужно. Попробуйте это вместо этого:

#!/bin/bash

for i in *; do
    if [[ -f "$i" ]];  then
        if [[ "$i"  ==  *.old ]]; then
            rm -v "$i" || echo "rm failed for $i"
        else
            echo "$i doesn't have an .old extension"
        fi
        cp -v "$i" "$i".old
    else
        echo "$i is not a file"
    fi 
done

Обратите внимание, что я добавил -vк операторам rmи cp which does the same thing as what you were doing with yourecho`.

Это не идеально, поскольку, когда вы найдете, например, file.oldчто это будет удалено, а позже скрипт попытается скопировать его и потерпит неудачу, так как файл больше не существует. Тем не менее, вы не объяснили, что на самом деле пытается сделать ваш сценарий, поэтому я не могу исправить это для вас, если вы не скажете нам, что вы действительно пытаетесь выполнить.

Если вы хотите: i) удалить все файлы с .oldрасширением и ii) добавить .oldрасширение к любым существующим файлам, у которых его нет, все, что вам действительно нужно, это:

#!/bin/bash

for i in *.old; do
    if [[ -f "$i" ]]; then
        rm -v "$i" || echo "rm failed for $i"
    else
        echo "$i is not a file"
    fi 
done
## All the ,old files have been removed at this point
## copy the rest
for i in *; do
    if [[ -f "$i" ]]; then
        ## the -v makes cp report copied files
        cp -v "$i" "$i".old
    fi
done
terdon
источник
Я пытаюсь сделать резервные копии файлов в file.old, но в то же время в rm любые файлы, заканчивающиеся на .old.old или .old.old.old или .old.old.old и т. Д. В командной строке, которую я использую, rm *.old.*удаляются эти файлы. но не файл резервной копии file.old. Я пытаюсь сделать это в моем сценарии. Спасибо
Дон
1
@ Пожалуйста, отредактируйте свой вопрос и объясните это более подробно. Включите примеры имен файлов и того, что вы хотели бы с ними случиться после запуска вашего скрипта. В идеале заходите в чат и пингуйте меня там, чтобы мы могли это обсудить.
Тердон
8

Единственные случаи rm $oldfileмогли не то, когда ваше имя файла содержит любой символ IFS(пробел, табуляция, перевод строки) или любой Глоб символ ( *, ?, []).

Если присутствует какой-либо символ, IFSоболочка выполнит разбиение по словам и на основе присутствия глобальных символов расширения имени пути в расширении переменной.

Так, например, если имя файла foo bar.old., переменная oldfileбудет содержать foo bar.old..

Когда вы делаете:

rm $oldfile

shell сначала разбивает расширение oldfileна пространство на два слова, fooи bar.old.. Таким образом, команда становится:

rm foo bar.old.

что, очевидно, приведет к неожиданному результату. Кстати, если у вас есть какие - либо подстановка операторов ( *, ?, []) в разложении, то путь к файлу расширение будет сделано слишком.

Вам нужно заключить переменные в кавычки, чтобы получить желаемый результат:

rm "$oldfile"

Теперь разделение слов или расширение пути не будет выполнено, поэтому вы должны получить желаемый результат, т.е. нужный файл будет удален. Если любое имя файла начинается с -, то выполните:

rm -- "$oldfile"

Вы можете спросить, почему нам не нужно заключать в кавычки переменные, когда они используются внутри [[, причина в том, [[что это bashключевое слово, которое обрабатывает внутреннее расширение переменной, сохраняя расширение литералом.


Теперь пара моментов:

  • Вы должны перенаправить STDERR ( exec 2>errorfile) перед rmкомандой, иначе [[ -s errorfile ]]проверка выдаст ложные срабатывания

  • Вы использовали [ -s $errorfile ], вы используете расширение переменной $errorfile, которое было бы NUL, данная errorfileпеременная нигде не определена. Возможно, вы имели в виду, просто [ -s errorfile ]на основе перенаправления STDERR

  • Если переменная errorfileопределена во время использования [ -s $errorfile ], она снова захлебнется вышеупомянутыми случаями IFSи смещается, потому что, в отличие от этого [[, [внутренне не обрабатываетсяbash

  • В более поздней части скрипта вы пытаетесь cpиспользовать уже удаленный файл (опять же без кавычек переменной), это не имеет никакого смысла, вы должны проверить этот патрон и внести необходимые исправления в зависимости от вашей цели.

heemayl
источник