Tak więc usunąłem mój folder domowy (a ściślej wszystkie pliki, do których miałem dostęp do zapisu). Stało się to, że miałem
build="build"
...
rm -rf "${build}/"*
...
<do other things with $build>
w skrypcie bash i, po nieużywaniu $build
, usuwając deklarację i wszystkie jej zastosowania - ale rm
. Bash z radością się rozwija rm -rf /*
. Tak.
Czułam się głupio, zainstalowałam kopię zapasową, przerobiłam utraconą pracę. Próbuję przejść obok wstydu.
Zastanawiam się: jakie są techniki pisania skryptów bash, aby takie błędy nie mogły się zdarzyć, a przynajmniej były mniej prawdopodobne? Na przykład, gdybym napisał
FileUtils.rm_rf("#{build}/*")
w skrypcie Rubiego tłumacz narzekałby, że build
nie został zadeklarowany, więc język mnie chroni.
To, co rozważałem w bashu, oprócz korygowania rm
(które, jak wspomina wiele odpowiedzi w powiązanych pytaniach, nie jest bezproblemowe):
rm -rf "./${build}/"*
To zabiłoby moją obecną pracę (repozytorium Git), ale nic więcej.- Wariant / parametryzacja
rm
tego wymaga interakcji, gdy działa poza bieżącym katalogiem. (Nie można znaleźć żadnego.) Podobny efekt.
Czy to jest, czy istnieją inne sposoby pisania skryptów bash, które są „solidne” w tym sensie?
źródło
rm -rf "${build}/*
bez względu na to, dokąd idą znaki cudzysłowu.rm -rf "${build}
zrobi to samo z powoduf
.set -u
nie jest tak marszczony, jakset -e
jest, ale nadal ma swoje wady.#! /usr/bin/env ruby
na górze każdego skryptu powłoki i zapomnij o bash;)Odpowiedzi:
lub
Spowodowałoby to, że bieżąca powłoka traktowałaby ekspansje zmiennych nieustalonych jako błąd:
set -u
iset -o nounset
są opcjami powłoki POSIX .Pusta wartość to nie wywoła błędu chociaż.
W tym celu użyj
Rozwinięcie
${variable:?word}
spowoduje rozwinięcie do wartości,variable
chyba że jest puste lub nieuzbrojone. Jeśli jest pusty lub rozbrojony,word
zostanie wyświetlony przy standardowym błędzie, a powłoka potraktuje rozszerzenie jako błąd (polecenie nie zostanie wykonane, a jeśli uruchomione w powłoce nieinteraktywnej, zostanie zakończone). Pozostawienie:
wyjścia spowodowałoby błąd tylko dla wartości nieustawionej, tak jak poniżejset -u
.${variable:?word}
jest rozszerzeniem parametru POSIX .Żadne z nich nie spowodowałoby zakończenia interaktywnej powłoki, chyba że
set -e
(lubset -o errexit
) również działało.${variable:?word}
powoduje zamknięcie skryptów, jeśli zmienna jest pusta lub nieuzbrojona.set -u
spowodowałoby zamknięcie skryptu, gdyby był używany razem zset -e
.Co do twojego drugiego pytania. Nie ma możliwości ograniczenia,
rm
aby nie działać poza bieżącym katalogiem.Implementacja GNU
rm
ma--one-file-system
opcję, która powstrzymuje go przed rekurencyjnym usuwaniem zamontowanych systemów plików, ale jest to tak blisko, jak uważam, że możemy uzyskać bez zawijaniarm
wywołania w funkcji, która faktycznie sprawdza argumenty.Na marginesie:
${build}
jest dokładnie równoważny,$build
chyba że rozwinięcie następuje jako część ciągu, w którym znak następujący bezpośrednio jest poprawnym znakiem w nazwie zmiennej, na przykład w"${build}x"
.źródło
${build:?msg}
czy${build?msg}
? 2) W kontekście czegoś takiego jak skrypty budowania, myślę, że użycie innego narzędzia niż rm do bezpieczniejszego usuwania byłoby w porządku: wiemy , że nie chcemy pracować poza bieżącym katalogiem, dlatego wyrażamy to wyraźnie, używając ograniczonego Komenda. Generalnie nie ma potrzeby zwiększania bezpieczeństwa rm.:
(spowodowałoby to błąd tylko dla zmiennych nieustawionych ). Nie odważę się pisać narzędzia, które radziłoby sobie z usuwaniem plików wyłącznie pod określoną ścieżką, we wszystkich okolicznościach. Analiza ścieżek i dbanie / nie dbanie o dowiązania symboliczne itp. Jest obecnie dla mnie trochę zbyt skomplikowane.Mam zamiar zasugerować normalne sprawdzanie poprawności za pomocą
test
/[ ]
Byłbyś bezpieczny, gdybyś napisał swój skrypt jako taki:
Te
[ -n "${build}" ]
kontrole to"${build}"
jest długość łańcucha niezerowe .Jest
||
to logiczny operator OR w bash. Powoduje uruchomienie innej komendy, jeśli pierwsza nie powiodła się.W ten sposób
${build}
były puste / niezdefiniowane / itp. skrypt zostałby zakończony (z kodem powrotu 1, co jest błędem ogólnym).To również ochroniłoby cię na wypadek, gdybyś usunął wszystkie zastosowania,
${build}
ponieważ[ -n "" ]
zawsze będzie to fałsz.Zaletą używania
test
/[ ]
jest to, że istnieje wiele innych bardziej znaczących kontroli, których może również używać.Na przykład:
źródło
${variable:?word}
proponuje @Kusalananda?test
(tj.[
) Ma wiele innych istotnych sprawdzeń, takich jak-d
(argument jest katalogiem),-f
(argument jest plikiem),-O
(plik jest własnością bieżącego użytkownika) i tak dalej.${build:?word}
wraz? Ten${variable:?word}
format nie chroni cię, jeśli usuniesz wszystkie wystąpienia zmiennej.if
. 2) „czy nie usunąłbyś również $ {build:? Word} wraz z nim” - scenariusz jest taki, że przegapiłem użycie zmiennej. Używanie${v:?w}
uchroniłoby mnie przed uszkodzeniem. Gdybym usunął wszystkie zwyczaje, nawet zwykły dostęp nie byłby oczywiście szkodliwy.W twoim konkretnym przypadku w przeszłości przerabiałem „usuwanie”, aby zamiast tego przenosić pliki / katalogi (zakładając, że / tmp znajduje się na tej samej partycji co katalog):
Za kulisami przenosi referencje do plików /
$trashdir
katalogów najwyższego poziomu ze źródeł do struktur katalogów docelowych na tej samej partycji i nie spędza czasu na chodzeniu po strukturze katalogów i zwalnianiu bloków dyskowych dla poszczególnych plików. Powoduje to znacznie szybsze czyszczenie, gdy system jest w użyciu, w zamian za nieco wolniejszy restart (/ tmp jest czyszczony przy ponownym uruchomieniu).Alternatywnie, wpis crona do okresowego czyszczenia /tmp/.trash-$USER powstrzyma / tmp przed zapełnieniem, dla procesów (np. Kompilacji), które zajmują dużo miejsca na dysku. Jeśli twój katalog znajduje się na innej partycji jako / tmp, możesz utworzyć podobny katalog / tmp na swojej partycji i zamiast tego mieć cron clean.
Najważniejsze jest jednak, że jeśli w jakikolwiek sposób spieprzysz zmienne, możesz odzyskać zawartość przed czyszczeniem.
źródło
/tmp
znajduje się na innej partycji (myślę, że zawsze jest dla mnie, przynajmniej dla skryptów działających w przestrzeni użytkownika); następnie folder kosza musi zostać zmieniony (i nie skorzysta z obsługi systemu operacyjnego/tmp
).mktemp -d
tego katalogu tymczasowego bez stąpania po palcach innego procesu (i poprawnie honorować$TMPDIR
).Użyj podstawienia parametrów bash, aby przypisać wartości domyślne, gdy zmienne są niezainicjowane, np .:
rm -rf $ {zmienna: - "/ nonexistent"}
źródło
Zawsze staram się zaczynać skrypty Bash od linii
#!/bin/bash -ue
.-e
oznacza „niepowodzenie przy pierwszym niepowodzeniu e rrr ”;-u
oznacza „nie na pierwszym użyciu u ndeclared zmienną”.Znajdź więcej szczegółów w świetnym artykule Użyj nieoficjalnego trybu ścisłego bash (chyba, że debugujesz Looove) . Również autor zaleca używanie,
set -o pipefail; IFS=$'\n\t'
ale dla moich celów jest to przesada.źródło
Ogólna rada, aby sprawdzić, czy twoja zmienna jest ustawiona, jest przydatnym narzędziem do zapobiegania tego rodzaju problemom. Ale w tym przypadku było prostsze rozwiązanie.
Najprawdopodobniej nie ma potrzeby globalizowania zawartości
$build
katalogu, aby je usunąć, ale nie samego$build
katalogu. Więc jeśli pominąłeś to, co obce,*
wówczas nieustawiona wartość zamieniłaby się wrm -rf /
, że domyślnie większość implementacji rm w ostatniej dekadzie odmówiłaby wykonania (chyba że wyłączysz tę ochronę za pomocą opcji GNU rm--no-preserve-root
).Omijając końcowego znaku
/
, jak również skutkowałobyrm ''
która spowodowałaby komunikat o błędzie:Działa to nawet jeśli twoje polecenie rm nie implementuje ochrony
/
.źródło
Myślisz w kategoriach języka programowania, ale bash jest językiem skryptowym :-) Więc zastosuj zupełnie inny paradygmat instrukcji dla zupełnie innego paradygmatu języka .
W tym przypadku:
Ponieważ
rmdir
odmówi usunięcia niepustego katalogu, musisz najpierw usunąć członków. Wiesz, kim są ci członkowie, prawda? Prawdopodobnie są to parametry twojego skryptu lub pochodzą z parametru, więc:Teraz, jeśli umieścisz tam inne pliki lub katalogi, takie jak plik tymczasowy lub coś, czego nie
rmdir
powinieneś mieć , spowoduje to błąd. Postępuj właściwie, a następnie:Ten sposób myślenia mi dobrze służył, ponieważ ... tak, byłem tam, zrobiłem to.
źródło
make clean
użyje niektórych symboli wieloznacznych (chyba że bardzo sumienny kompilator utworzy listę wszystkich tworzonych plików). Wydaje mi się też, żerm -rf ${build}/${parameter}
problem przesunął się nieco w dół.make
tym pytaniu nie ma nic . Napisanie odpowiedzi, która ma zastosowanie tylko do,make
byłoby bardzo konkretnym i zaskakującym założeniem. Moja odpowiedź działa na przypadki inne niżmake
tworzenie oprogramowania, inne niż konkretny problem z nowicjuszem, który miałeś przez usunięcie swoich rzeczy.