Elencherà sempre i file che rm rimuoverà?


33

Qualcosa che sento di dover sapere con certezza: se lo farò ls <something>, rm <something>rimuoverò esattamente gli stessi file lsvisualizzati? Vi sono circostanze in cui è rmpossibile rimuovere i file che lsnon sono stati visualizzati? (Questo è nel 18.04 bash)

Modifica: grazie a tutti quelli che hanno risposto. Penso che la risposta completa sia una combinazione di tutte le risposte, quindi ho accettato la risposta più votata come "la risposta".

Cose inaspettate che ho imparato lungo la strada:

  • ls non è così semplice come si potrebbe pensare nella gestione dei suoi argomenti
  • In una semplice installazione non complicata di Ubuntu, alias .bashrc ls
  • Non dare un nome ai tuoi file che iniziano con un trattino perché possono sembrare argomenti di comando, e nominarne uno -r lo sta chiedendo!

8
Sono un po 'sorpreso che rmnon abbia una --dry-runbandiera ...
fkraiem

20
@Rinzwind Perché sarebbe find -deletemeglio di rm? Dici "Ecco perché" , ma per me non è del tutto chiaro a cosa si riferisca. Nota inoltre che la tua findchiamata eliminerà tutti i file in modo ricorsivo nella directory corrente, dove rmeliminerà semplicemente i file nella directory immediata. Inoltre -name *è un no-op. Tutto sommato, sono abbastanza perplesso dal tuo consiglio ...
marcelm,

3
@marcelm Penso che il consiglio per l'uso findsia perché puoi eseguirlo, vedere tutti i file e quindi eseguire lo stesso comando con -delete. Dato che hai già visto i risultati find, non dovrebbe esserci alcuna ambiguità su ciò che verrà rimosso (in realtà mi piacerebbe sentire maggiori dettagli su questo sotto forma di una risposta)
Scribblemacher,

5
@Scribblemacher "... puoi eseguirlo, vedere tutti i file e quindi eseguire lo stesso comando con -delete" - Ma come è meglio che in esecuzione ls <filespec>, seguito da rm <filespec>(che l'OP sa già come fare)?
marcelm,

12
@Rinzwind "Ciò risolve la risposta di choroba per un istante in cui viene creato un file dopo ls e prima di rm." - No, non lo fa. Se esegui find ... -printprima per confermare quali file verranno eliminati, quindi find ... -deleteeliminerai comunque i file creati tra i due comandi. Se si utilizzano entrambi -printe -delete, non si ottiene la conferma, solo un rapporto di fatto di ciò che è stato eliminato (e si potrebbe anche utilizzare rm -v).
marcelm,

Risposte:


40

Bene, entrambi lse rmoperare sugli argomenti che sono passati a loro.

Questi argomenti possono essere un semplice file, in modo ls file.exte rm file.extoperano sullo stesso file e il risultato è chiaro (lista il file / eliminare il file).

Se invece l'argomento è una directory, ls directoryelenca il contenuto della directory mentre rm directorynon funzionerà così com'è (ovvero rmsenza flag non è possibile rimuovere le directory, mentre se lo fa rm -r directory, elimina ricorsivamente tutti i file in directory e la directory stessa ).

Ma tieni presente che gli argomenti della riga di comando possono essere soggetti all'espansione della shell , quindi non è sempre garantito che gli stessi argomenti vengano passati a entrambi i comandi se contengono caratteri jolly, variabili, output da altri comandi, ecc.

Come esempio estremo pensare ls $(rand).txte rm $(rand).txt, gli argomenti sono "gli stessi" ma i risultati sono abbastanza diversi!


3
Considera anche lsvs rm *dove ci sono file "nascosti" (punto), anche se anche questo non è affatto un confronto equo in quanto non ho scritto ls *. Ma non ha rmsenso da solo, quindi l'intera cosa è davvero mele e arance. Se ho capito bene, questo è il nocciolo della tua risposta, quindi un buon lavoro :)
Lightness Races con Monica

5
Un altro commento sul lsvs rm -r: il comando ls <directory>non mostrerà i file nascosti all'interno della directory, ma rm -r <directory> si elimina anche i file nascosti.
Daniel Wagner,

1
@LightnessRacesinOrbit lsnon li elencherà (a meno che non sia aliasato ls -a) e rm *non li eliminerà (a meno che tu non abbia dotglobimpostato).
Smetti di fare del male a Monica il

20

Se stai pensando a qualcosa come ls foo*.txtvs. rm foo*.txt, allora sì, mostreranno e rimuoveranno gli stessi file. La shell espande il glob e lo passa al comando in questione, e i comandi funzionano sui file elencati. Uno li elenca, uno li rimuove.

L'ovvia differenza è che se uno di quei file fosse una directory, lsne elencherebbe il contenuto, ma rmnon riuscirà a rimuoverlo. Questo di solito non è un problema, poiché rmrimuoverebbe meno di quello che è stato mostrato ls.

Il grosso problema qui viene dall'esecuzione ls *o rm *in una directory contenente nomi di file che iniziano con un trattino . Avrebbero espandersi per le linee di comando dei due programmi, come se li hai scritto voi stessi, e lsavrebbero preso -ril significato di "ordinamento inverso", mentre rmavrebbe preso -ra significare una rimozione ricorsiva. La differenza è importante se si dispone di sottodirectory con almeno due livelli di profondità. ( ls *mostrerà i contenuti delle directory di primo livello, ma rm -r *tutto supererà anche il primo livello secondario).

Per evitarlo, scrivi globs permissivi con un iniziale ./per indicare la directory corrente e / o metti a --per segnalare la fine dell'elaborazione delle opzioni prima del glob (cioè rm ./*o rm -- *).

Con un glob come *.txt, in realtà non è un problema poiché il punto è un carattere di opzione non valido e causerà un errore (fino a quando qualcuno non espande le utility per inventarne un significato), ma è comunque più sicuro metterlo ./lì.


Ovviamente potresti anche ottenere risultati diversi per i due comandi se hai cambiato le opzioni di globbing della shell, o hai creato / spostato / rimosso file tra i comandi, ma dubito che tu abbia inteso uno di questi casi. (Avere a che fare con file nuovi / spostati sarebbe estremamente complicato da eseguire in sicurezza.)


1
Grazie. Questo sembra evidenziare un problema con l'intera sintassi della riga di comando: se dai un nome ai file che iniziano con un trattino stai navigando in acque pericolose. Chissà quali comandi potresti usare in futuro, molto tempo dopo aver dimenticato i file.
B.Tanner

1
@ B.Tanner, sì. In un certo senso, il problema è che gli argomenti della riga di comando sono solo stringhe semplici. Se il sistema è stato progettato oggi, potrebbe esserci più struttura in modo che il programma eseguito possa sapere se un argomento dovrebbe essere un flag di opzione o no. Ci sono anche altri problemi che derivano dalla struttura molto lassista dei nomi dei file. C'è un saggio molto approfondito su ciò di Dwheeler , ma devo avvertire che leggerlo farà male. (O dal dettaglio, o dalla totale terribilezza di ciò che può andare storto.)
ilkkachu,

3
Non posso resistere, me l'hai venduto, me ne vado a leggerlo ora, con ricordi del tempo in cui sono riuscito a ottenere un carattere di ritorno a capo alla fine di molti nomi di file (cercando di vedere se potevo costruire un file batch di Windows che può anche essere eseguito come uno script bash ... Non ho visto quello in arrivo!)
B.Tanner

17

Lasciando da parte il comportamento della shell, concentriamoci solo su ciò rmche lspossiamo affrontare. Almeno un caso in cui lsverrà mostrato ciò che rmnon è possibile rimuovere riguarda i permessi di directory e l'altro - directory speciali .e ...

Autorizzazioni per le cartelle

rmè un'operazione su una directory, perché rimuovendo un file, stai cambiando il contenuto della directory (o in altre parole l'elenco delle voci della directory, poiché d irectory non è altro che un elenco di nomi di file e inode ). Ciò significa che è necessario disporre delle autorizzazioni di scrittura su una directory. Anche se sei il proprietario del file , senza le autorizzazioni della directory non puoi rimuovere i file. È vero anche il contrario : rmpuoi rimuovere file che potrebbero essere di proprietà di altri, se sei il proprietario della directory.

Quindi potresti benissimo aver letto ed eseguire le autorizzazioni su una directory, il che ti permetterà di attraversare la directory e visualizzare i contenuti in modo corretto, ad esempio ls /bin/echo, ma non puoi rm /bin/echose non sei il proprietario /bin o eleva i tuoi privilegi sudo.

E vedrai casi come questo ovunque. Ecco uno di questi casi: https://superuser.com/a/331124/418028


Elenchi speciali "." e '..'

Un altro caso speciale è .e ..directory. Se lo fai ls .o ls .., ti mostrerà felicemente i contenuti, ma rmnon è consentito ingerirli:

$ rm -rf .
rm: refusing to remove '.' or '..' directory: skipping '.'

15

Se digiti ls *e quindi rm *, è possibile rimuovere più file di quelli lsmostrati: potrebbero essere stati creati nel breve intervallo di tempo tra la fine di lse l'inizio di rm.


1
Questo è un caso probabile in /tmpcui molte applicazioni possono creare file temporanei, quindi è sempre possibile in entrambi i comandi *. Tuttavia, alcune applicazioni rendono anche i file anonimi unlink()inserendoli mantenendo aperto l'handle dei file, quindi potrebbe essere visualizzato ls *ma rm *potrebbe non essere rilevato.
Sergiy Kolodyazhnyy,

1
@OrangeDog Ti manca il punto. Stiamo parlando delle condizioni di gara
Sergiy Kolodyazhnyy

1
@OrangeDog Dovrò verificarlo dal momento che sono al telefono in questo momento, ma il punto rimane valido anche con *la differenza tra ciò lsche mostrerebbe e ciò rmsu cui opera, perché l'elenco dei contenuti della directory è cambiato nel mezzo.
Sergiy Kolodyazhnyy,

1
Anche se esegui un solo comando, esiste una condizione di competizione tra l'espansione degli argomenti e la loro eliminazione.
Smetti di fare del male a Monica il

1
@OrangeDog Sono d'accordo. Poiché l'espansione dei caratteri jolly funziona su nomi di file esistenti, sì, esiste una condizione di competizione tra l'espansione della shell del carattere jolly e un comando che lo elabora, quindi ls *potrebbe effettivamente mostrare il nome file che è già sparito.
Sergiy Kolodyazhnyy,

9

ls *e rm *non sono responsabili dell'espansione del glob - ciò viene fatto dalla shell prima di passarlo al comando.

Ciò significa che è possibile utilizzare qualsiasi comando con l'elenco di file espanso, quindi utilizzerei qualcosa che fa il meno possibile.

Quindi un modo migliore per farlo (o almeno, un altro modo) è saltare l'uomo di mezzo.

echo *ti mostrerà esattamente cosa verrebbe passato al tuo rmcomando.


2
Grazie, mi piace l'idea di usare l'eco invece di ls in questo scenario.
B.Tanner

3
O printf "%s\n" *per avere una visione inequivocabile di nomi di file con spazi. (O %qinvece di occuparsi di newline e anche di controllare i personaggi, a spese di output più
brutti

4

Che ne dite di:

$ mkdir what
$ cd what
$ mkdir -p huh/uhm ./-r
$ ls *
uhm
$ rm *
$ ls
-r
$ ls -R
.:
-r

./-r:

Fondamentalmente i caratteri jolly che si espandono in elementi che iniziano con -(o elementi inseriti manualmente che iniziano con -ma che assomigliano un po 'più a barare) possono essere interpretati in modo diverso da lse rm.


4

Ci sono casi limite in cui ciò che lsmostra non è ciò che rmrimuove. Uno piuttosto estremo, ma fortunatamente benigno è se l'argomento che passi è un collegamento simbolico a una directory: lsti mostrerà tutti i file nella directory symlinked, mentre rmrimuoverà il symlink, lasciando intatta la directory originale e il suo contenuto:

% ln -s $HOME some_link
% ls some_link    # Will display directory contents  
bin    lib    Desktop ...
% rm some_link
% ls $HOME
bin    lib    Desktop ...

1
Huh. ln -s $HOME some_link; ls some_linkuscite some_link@per me, ma ho fatto le lsalias ls -F. Apparentemente, -Fcambia il comportamento in modo da mostrare il collegamento invece di dereferenziarlo. Non me l'aspettavo.
marcelm,

Infatti! Ad ls -lesempio, indirizza anche il collegamento, non la destinazione ... ci devono essere modi per ispezionare il collegamento stesso.
alexis,

1
L'aggiunta di opzioni alla domanda apre troppe possibilità: la mia risposta riguarda il comportamento non modificato di lse rm.
alexis,

Se dici ls some_link/,  ls -H some_linko ls -L some_link, elencherà il legato-a directory, anche se si aggiunge -Fo -l. Al contrario (sorta di), -ddice di guardare una directory piuttosto che il suo contenuto; confrontare ls -l /tmpe ls -ld /tmp.
G-Man dice "Reinstate Monica" l'

Certo, puoi aggiungere flag che cambiano il comportamento di ls. Stai praticamente mostrando perché enumerare comportamenti con bandiere diverse non merita il disturbo per questa domanda ...
alexis

4

Se lo fai solo al lsposto di ls -a, yes rmpuò rimuovere i file nascosti che non hai visto lssenza -a.

Esempio :

Secondo :

dir_test
├── .test
└── test2

ls dir_test : visualizzerà solo test2

ls -A dir_test : visualizzerà test2 + .test

rm -r dir_test : rimuoverà tutto (.test + test2)

Spero che ti possa aiutare.


Potete fornire un esempio? Perché normalmente, rm *non rimuoverà i dotfile. Se lo fa, ls *li mostrerà anche.
marcelm,

No, ls *non visualizzare i file nascosti.
DevHugo,

Ma sì, è un po 'confuso, ho aggiunto alcuni esempi.
DevHugo,

1
ls -aelencherà ., .., .teste test2. Potresti voler cambiare il tuo esempio da usare ls -A, che elenca tutto tranne . e ..(cioè, solo .teste test2).
G-Man dice "Reinstate Monica" l'

3

Ci sono già molte buone risposte, ma voglio aggiungere alcune intuizioni più profonde.

Ponetevi la domanda: a quanti parametri vengono passati ls, se si scrive

ls *

...? Si noti che il lscomando non ottiene il *parametro as se ci sono file che *possono essere espansi. Invece, la shell esegue prima il globbing prima di invocare il comando, quindi il lscomando ottiene effettivamente tutti i parametri quanti sono i file corrispondenti al globbing. Per eliminare il globbing, citare il parametro.

Questo è vero per qualsiasi comando: echo *vs echo '*'.

C'è uno script, chiamalo countparams.shper testare l'effetto. Ti dice quanti parametri ha superato e li elenca.

#!/bin/bash
echo "This script was given $# parameters."
arr=( "$@" )
for ((i=0;i<$#;i++)); do
        echo "Parameter $((i+1)): ${arr[$i]}"
done

Renderlo eseguibile ed eseguirlo ./countparams.sh *. Impara dal suo output!


1

Il glob si espanderà allo stesso modo entrambe le volte, se il contenuto della directory è lo stesso in quei due tempi diversi.


Se vuoi davvero controllare cosa verrà rimosso, usa rm -i *.txt. Ti verrà richiesto separatamente per ogni file prima di (tentando di) rimuoverlo.

Questo è garantito per essere sicuro contro le condizioni di gara:
        ls *.txt/ viene creato un nuovo file / rm *.txt
perché ti viene richiesto ogni file dallo stesso programma che sta eseguendo la rimozione.


Questo è troppo ingombrante per l'uso normale, e se si alias rmper rm -i, vi ritroverete con \rmo rm -fabbastanza spesso. Ma vale la pena ricordare almeno che esiste una soluzione alle condizioni di gara. (È persino portabile su sistemi non GNU: POSIX rm(1)specifica l' -iopzione .)

Un'altra opzione sarebbe una matrice bash:, to_remove=(*.txt)quindi chiedere all'utente di confermare (forse dopo averlo fatto ls -ld -- "${to_remove[@]}"), quindi rm -- "${to_remove[@]}". Quindi l'espansione glob viene eseguita una sola volta e l'elenco viene passato testualmente rm.

Un'altra opzione praticamente utilizzabile è GNU rm -I( pagina man ), che richiede di rimuovere più di 4 elementi. (Ma non ti mostra la lista, solo il totale.) Uso alias rm='rm -I'sul mio desktop.

È una buona protezione contro il ritorno di una diteggiatura grassa con un modello mezzo tipizzato che corrisponde troppo. Ma usare lsprima è generalmente buono in una directory che possiedi, o su un sistema a utente singolo, e quando non ci sono processi in background che potrebbero creare in modo asincrono nuovi file lì. Per evitare la diteggiatura del grasso, non digitare rm -rf /foo/bar/bazda sinistra a destra. rm -rf /è speciale, ma rm -rf /usrnon lo è! Lasciare la -rfparte o iniziare con lse aggiungere la rm -rfparte solo dopo aver digitato il percorso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.