rm działa w wierszu poleceń, ale nie w skrypcie

11

Kiedy robię to rm *.old.*z wiersza poleceń, usuwa się poprawnie, ale kiedy robię to w dalszej części mojego skryptu, nie rm wszystkich *.old.*plików.

Co jest nie tak w moim skrypcie 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
Don
źródło

Odpowiedzi:

4

Jeśli rozumiem, co robisz (usuń pliki z .oldsufiksem i zrób kopię istniejących plików z .oldsufiksem), możesz zamiast tego użyć find:

#!/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 0zatrzymuje szukanie w podkatalogach polecenia find, -type fszuka tylko zwykłych plików; -printftworzy wiadomości ( %Pjest znaleziona nazwa pliku). -exec cpWywołuje funkcję kopiowania i '{}'to nazwa

Nick Sillito
źródło
14

W twoim skrypcie występują różne możliwe punkty awarii. Przede wszystkim rm *.old*użyje globowania, aby utworzyć listę wszystkich pasujących plików, które mogą poradzić sobie z nazwami plików zawierającymi białe znaki. Jednak twój skrypt przypisuje zmienną do każdego wyniku globu i robi to bez cytowania. To się zepsuje, jeśli nazwy plików zawierają spacje. Na przykład:

$ 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'

Jak widać, pętla nie powiodła się dla pliku ze spacjami w nazwie. Aby zrobić to poprawnie, musisz zacytować zmienną:

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

Ten sam problem dotyczy prawie każdego użycia $iskryptu. Powinieneś zawsze cytować zmienne .

Kolejnym możliwym problemem jest to, że wydaje się oczekiwać, że *.old.*pasuje do plików z rozszerzeniem .old. Tak nie jest. Dopasowuje „0 lub więcej znaków” ( *), a następnie a ., następnie „stary”, następnie inny, .a następnie „ponownie 0 lub więcej znaków”. Oznacza to, że będzie to nie pasują do czegoś podobnego file.old, ale tylko coś w rodzaju `file.old.foo:

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

Więc nie ma przeciwnika file.old. W każdym razie skrypt jest o wiele bardziej złożony niż jest to konieczne. Spróbuj zamiast tego:

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

Zauważ, że dodałem -vdo instrukcji echo rmi cp which does the same thing as what you were doing with your.

Nie jest to idealne, ponieważ na przykład, gdy znajdziesz, file.oldże zostanie on usunięty, a później skrypt spróbuje go skopiować i nie powiedzie się, ponieważ plik już nie istnieje. Jednak nie wyjaśniłeś, co skrypt naprawdę próbuje zrobić, więc nie mogę tego naprawić, chyba że powiesz nam, co naprawdę próbujesz osiągnąć.

Jeśli chcesz: i) usunąć wszystkie pliki z .oldrozszerzeniem oraz ii) dodać .oldrozszerzenie do wszystkich istniejących plików, które go nie mają, wszystko czego naprawdę potrzebujesz to:

#!/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
źródło
Próbuję wykonać kopię zapasową plików do file.old, ale jednocześnie z wszystkimi plikami, które kończą się na .old.old lub .old.old.old lub .old.old.old itp. W wierszu polecenia używam, rm *.old.*który usuwa te pliki ale nie plik kopii zapasowej file.old. Próbuję to zrobić w moim skrypcie. Dzięki
Don
1
@ Nie edytuj pytania i wyjaśnij to bardziej szczegółowo. Podaj przykładowe nazwy plików oraz informacje o tym, co chcesz z nimi zrobić po uruchomieniu skryptu. Najlepiej, wejdź na czat i pinguj mnie tam, abyśmy mogli to omówić.
terdon
8

Jedyne przypadki, rm $oldfilemoże nie są, gdy nazwa pliku zawiera dowolny znak IFS(spacja, tabulacjami) lub dowolny znak glob ( *, ?, []).

Jeśli IFSobecny jest jakikolwiek znak, powłoka wykona dzielenie słów i na podstawie obecności interpretacji nazw ścieżek znaków globujących na rozwinięciu zmiennej.

Na przykład, jeśli nazwa pliku to foo bar.old., zmienna oldfilezawierałaby foo bar.old..

Kiedy to zrobisz:

rm $oldfile

shell na początku dzieli ekspansję oldfileprzestrzeni na dwa słowa, fooi bar.old.. Zatem polecenie staje się:

rm foo bar.old.

co oczywiście doprowadziłoby do nieoczekiwanego rezultatu. Nawiasem mówiąc, jeśli masz żadnych nazw plików operatorów ( *, ?, []) w rozbudowie, a następnie ścieżka ekspansji byłoby zrobić też.

Aby uzyskać pożądany wynik, musisz podać zmienne:

rm "$oldfile"

Teraz nie byłoby dzielenia słów ani rozwijania nazw ścieżek, dlatego powinieneś uzyskać pożądany wynik, tj. Pożądany plik zostałby usunięty. Jeśli jakaś nazwa pliku zaczyna się od -, to:

rm -- "$oldfile"

Możesz zapytać, dlaczego nie musimy cytować zmiennych, gdy są używane w środku [[, ponieważ [[jest to bashsłowo kluczowe i obsługuje wewnętrzną ekspansję zmiennych, zachowując dosłowne rozwinięcie.


Teraz kilka punktów:

  • Powinieneś przekierować STDERR ( exec 2>errorfile) przed rmpoleceniem, w przeciwnym razie [[ -s errorfile ]]test dałby fałszywe alarmy

  • Użyłeś [ -s $errorfile ], używasz rozszerzenia zmiennej $errorfile, które byłoby NUL, ponieważ errorfilezmienna nie jest nigdzie zdefiniowana. Być może chodziło ci po prostu [ -s errorfile ]o przekierowanie STDERR

  • Jeśli zmienna errorfilejest zdefiniowana, podczas używania [ -s $errorfile ]ponownie IFSdusiłaby wyżej wymienione przypadki i globowanie, ponieważ w przeciwieństwie do tego [[, [nie jest obsługiwana wewnętrznie przezbash

  • W dalszej części skryptu próbujesz cpusunąć już pobrany plik (ponownie bez cytowania zmiennej), nie ma to żadnego sensu, powinieneś sprawdzić ten uchwyt i wprowadzić niezbędne poprawki w zależności od celu.

heemayl
źródło