rm працює в командному рядку, але не в сценарії


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припиняє команду find шукати у підкаталогах, -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 ., потім "старий", потім ще один, .а потім "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

Я намагаюся створити резервну копію файлів на 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

оболонка спочатку розбиває розширення 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вже видалити файл (знову ж таки, не цитуючи змінну), це не має сенсу, вам слід перевірити цей патрон і внести необхідні виправлення на основі вашої цілі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.