Czy są jakieś wady używania rm $ (ls) do usuwania plików?

13

Zastanawiałem się, czy używanie rm $(ls)do usuwania plików (lub również rm -r $(ls)do usuwania katalogów) było bezpieczne? Ponieważ na wszystkich stronach internetowych ludzie dają inne sposoby, aby to zrobić, nawet jeśli to polecenie wydaje się znacznie łatwiejsze niż inne polecenia.

posixKing
źródło
3
Podstawowa odpowiedź, nie. Nie mogę obsłużyć znaków specjalnych. Potrafię napisać odpowiedź, aby wyjaśnić to bardziej szczegółowo
Sergiy Kolodyazhnyy
5
touch 'foo -r .. bar'; rm $(ls); gdzie poszedł mój katalog nadrzędny? Jakie alternatywne rozwiązania są jeszcze bardziej skomplikowane? rm *jest o wiele łatwiejsze do pisania i myślenia oraz bezpieczniejsze (ale nie do końca bezpieczne; patrz odpowiedź Dennisa).
Peter Cordes,
1
Oprócz doskonałych odpowiedzi należy pamiętać, że lsmogą się one różnić między implementacjami, a zatem są niestandardowe. W zależności od potrzeb rozważ alternatywne rozwiązania, takie jak findi stat. Powinieneś używać go lswyłącznie do spożycia przez ludzi, nigdy do użycia przez inne polecenia lub skrypty.
Paddy Landau,

Odpowiedzi:

7

Co to ma robić?

  • ls wyświetla listę plików w bieżącym katalogu
  • $(ls)zastępuje dane wyjściowe lsmiejsc, które jako argumentrm
  • Zasadniczo rm $(ls)ma na celu usunięcie wszystkich plików w bieżącym katalogu

Co jest nie tak z tym obrazem ?

lsnie może poprawnie obsługiwać znaków specjalnych w nazwie pliku. Użytkownicy systemów uniksowych zazwyczaj zalecają stosowanie różnych podejść . Pokazałem to również w powiązanym pytaniu dotyczącym liczenia nazw plików . Na przykład:

$ touch file$'\n'name                                                                                                    
$ ls                                                                                                                     
file?name
$ rm $(ls)
rm: cannot remove 'file': No such file or directory
rm: cannot remove 'name': No such file or directory
$ 

Ponadto, jak słusznie wspomniano w odpowiedzi Denisa, nazwę pliku z wiodącymi myślnikami można interpretować jako argument rmpo zamianie, co nie pozwala na usunięcie nazwy pliku.

Co działa

Chcesz usunąć pliki w bieżącym katalogu. Więc użyj glob rm *:

$ ls                                                                                                                     
file?name
$ rm $(ls)
rm: cannot remove 'file': No such file or directory
rm: cannot remove 'name': No such file or directory
$ rm *
$ ls
$ 

Możesz użyć findpolecenia. To narzędzie jest często zalecane w przypadku więcej niż tylko bieżącego katalogu - może rekurencyjnie przechodzić przez całe drzewo katalogów i operować na plikach za pośrednictwem-exec . . .{} \;

$ touch "file name"                                
$ find . -maxdepth 1 -mindepth 1                                                                                         
./file name
$ find . -maxdepth 1 -mindepth 1 -exec rm {} \;                                                                          
$ ls
$ 

Python nie ma problemu ze znakami specjalnymi w nazwach plików, więc możemy to również zastosować (pamiętaj, że ten jest tylko dla plików, będziesz musiał użyć os.rmdir()i os.path.isdir()jeśli chcesz operować na katalogach):

python -c 'import os; [ os.remove(i) for i in os.listdir(".") if os.path.isfile(i) ]'

W rzeczywistości powyższe polecenie może zostać zmienione w funkcję lub alias ~/.bashrcdla zwięzłości. Na przykład,

rm_stuff()
{
    # Clears all files in the current working directory
    python -c 'import os; [ os.remove(i) for i in os.listdir(".") if os.path.isfile(i) ]'

}

Byłaby to wersja Perla

perl -e 'use Cwd;my $d=cwd();opendir(DIR,$d); while ( my $f = readdir(DIR)){ unlink $f;}; closedir(DIR)'
Sergiy Kolodyazhnyy
źródło
1
Twój "$(ls)"przykład działa tylko wtedy, gdy w katalogu jest tylko jeden plik. Równie dobrze możesz użyć uzupełniania tabulatorami, aby rozwinąć nazwę pliku, ponieważ jest to jedyne uzupełnienie.
Peter Cordes,
@PeterCordes rzeczywiście, z dziwnego powodu działa tylko z jednym plikiem. To jeszcze jeden argument przeciwko użyciu ls:) Będę go edytować
Sergiy Kolodyazhnyy
Nie nazwałbym tego dziwnym powodem: albo cytujesz, $(ls)aby wyłączyć dzielenie słów, albo pozwalasz na dzielenie słów (z katastrofalnymi skutkami). Jedynym czystym sposobem na ominięcie listy wielu ciągów bez traktowania danych jako kodu są zmienne tablicowe lub \0separator, ale sama powłoka nie może tego zrobić. Nadal IFS=$'\n'jest najmniej niebezpieczny, ale nie może się równać find -print0 | xargs -0. Lub grep -l --null. Lub unikniesz całego problemu z takimi rzeczami jak find -exec rm {} +. (Zwróć uwagę, +aby przekazać wiele argumentów do każdego wywołania rm; WAY bardziej wydajny).
Peter Cordes,
@PeterCordes Tak, całkowicie się tam zgodziłem. Ale IFS=$'\n'w tym przypadku również się nie powiedzie, ponieważ mam nową linię w nazwie pliku, więc dzielenie słów potraktuje to jako dwie nazwy plików zamiast jednej. Dziwnym powodem jest jednak fakt, że przy domyślnej IFSspacji, tabulacji, nowej linii, oryginale rm "$(ls)"również powinno się nie powieść, powinno to traktować tak jak powiedziałem nazwę pliku jako dwa osobne, ale tak się nie stało. Zazwyczaj używam findz -execlub find . . .-print0 | while IFS= read -d'' FILENAME ; do . . . donestruktury do czynienia z nazwami plików. Lub można użyć python, dodałem przykład tego.
Sergiy Kolodyazhnyy
"$(ls)"zawsze rozwija się do jednego argumentu, ponieważ cudzysłowy chronią ekspansję $(ls)przed dzieleniem słów. Tak jak chronią "$foo".
Peter Cordes,
26

Nie, to nie jest bezpieczne, a powszechnie stosowana alternatywa rm *nie jest dużo bezpieczniejsza.

Jest wiele problemów z rm $(ls). Jak już inni ujęli w swoich odpowiedziach, wynik lszostanie podzielony na znaki obecne w wewnętrznym separatorze pól .

Najlepszy scenariusz, po prostu nie działa. W najgorszym przypadku zamierzałeś usunąć tylko pliki (ale nie katalogi) - lub selektywnie usunąć niektóre pliki za pomocą -i- ale c -rfw bieżącym katalogu znajduje się plik o nazwie . Zobaczmy co się stanie.

$ mkdir a
$ touch b
$ touch 'c -rf'
$ rm -i $(ls)
$ ls
c -rf

Polecenie rm -i $(ls)miało usunąć tylko pliki i zapytać przed usunięciem każdego z nich, ale polecenie, które zostało ostatecznie wykonane, przeczytało

rm -i a b c -rf

więc zrobiło to coś zupełnie innego.

Pamiętaj, że rm *jest tylko nieznacznie lepszy. Przy takiej strukturze katalogów jak poprzednio będzie się ona zachowywać zgodnie z przeznaczeniem, ale jeśli masz plik o nazwie -rf, nadal nie masz szczęścia.

$ mkdir a
$ touch b
$ touch ./-rf
$ rm -i *
$ ls
-rf

Istnieje kilka lepszych alternatyw. Najłatwiejsze są tylko rm i globbing.

  • Komenda

    rm -- *
    

    będzie działać dokładnie zgodnie z przeznaczeniem, gdzie --sygnalizuje, że wszystko po nim nie powinno być interpretowane jako opcja.

    Jest to część wytycznych dotyczących składni narzędzi POSIX od ponad dwóch dekad. Jest szeroko rozpowszechniony, ale nie należy oczekiwać, że będzie obecny wszędzie.

  • Komenda

    rm ./*
    

    powoduje, że glob rozszerza się inaczej, a zatem nie wymaga wsparcia od wywoływanego narzędzia.

    W moim przykładzie z góry możesz zobaczyć polecenie, które ostatecznie zostanie wykonane przez wstępne echo .

    $ echo rm ./*
    rm ./a ./b ./-rf
    

    Czołowe ./zapobiega rm z przypadkowo leczenia żadnej z nazw takich opcji.

Dennis
źródło
1
Bardzo dobry punkt, nazwy plików z - po rozwinięciu stają się flagami do rm. +1
Sergiy Kolodyazhnyy
1
Nawet nastier: touch 'foo -rf .. bar. Nie sądzę, aby atakujący mógł uzyskać coś wyższego niż katalog nadrzędny, chyba że możemy wygenerować separator ścieżki w lsdanych wyjściowych.
Peter Cordes,
@Peter atakujący musiałby przede wszystkim mieć uprawnienia do zapisu, aby usunąć katalog patentowy, prawda?
Sergiy Kolodyazhnyy
1
@PeterCordes Nie jestem pewien, czy wszystkie wersje rm mają to zabezpieczenie przed awarią, ale w Ubuntu, openSUSE i Fedorze mówi rm: refusing to remove '.' or '..' directory: skipping '..'lub coś podobnego podczas próby usunięcia katalogu nadrzędnego.
Dennis
@Serg: atakujący wysyła tylko .zip z tą nazwą pliku i pozwala strzelić sobie w stopę, wyodrębniając go, a następnie próbując usunąć zawartość. Lub tworząc tę ​​nazwę pliku w / var / tmp lub coś takiego.
Peter Cordes,