Domanda

Esiste un modo semplice, in un ambiente UNIX piuttosto standard con bash, per eseguire un comando per eliminare tutti i file X tranne i più recenti da una directory?

Per fornire un esempio più concreto, immagina che un processo cron scriva un file (ad esempio, un file di registro o un backup tarato) in una directory ogni ora.Mi piacerebbe avere un altro lavoro cron in esecuzione che rimuova i file più vecchi in quella directory fino a quando non saranno meno di, diciamo, 5.

E tanto per essere chiari, è presente un solo file e non dovrebbe mai essere eliminato.

È stato utile?

Soluzione

I problemi con le risposte esistenti:

  • incapacità di gestire nomi di file con spazi incorporati o ritorni a capo.
    • nel caso di soluzioni che invocano rm direttamente su una sostituzione di comando senza virgolette (rm `...`), esiste un ulteriore rischio di globbing involontario.
  • incapacità di distinguere tra file e directory (vale a dire, se directory fosse tra i 5 elementi del filesystem modificati più di recente, avresti effettivamente conservato meno di 5 file e applicare rm alle directory fallirà).

la risposta di wnoise affronta questi problemi, ma la soluzione c'è GNU-specifico (e piuttosto complesso).

Ecco una pragmatica, Soluzione conforme a POSIX che viene fornito solo con un avvertimento:non può gestire nomi di file con embedded nuove righe - ma non la considero una preoccupazione reale per la maggior parte delle persone.

Per la cronaca, ecco la spiegazione del motivo per cui generalmente non è una buona idea analizzare ls produzione: http://mywiki.wooledge.org/ParsingLs

ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {}

Quanto sopra è inefficiente, Perché xargs deve invocare rm una volta per ogni nome del file.
Quella della tua piattaforma xargs potrebbe permetterti di risolvere questo problema:

Se hai GNU xargs, utilizzo -d '\n', che rende xargs considera ogni riga di input un argomento separato, ma passa tanti argomenti quanti ne stanno su una riga di comando subito:

ls -tp | grep -v '/$' | tail -n +6 | xargs -d '\n' -r rm --

-r (--no-run-if-empty) lo garantisce rm non viene richiamato se non c'è input.

Se hai BSD xargs (compreso su OS X), Puoi usare -0 gestire NUL-input separato, dopo aver prima tradotto i ritorni a capo NUL (0x0) caratteri., che passa anche (tipicamente) tutti i nomi di file subito (funzionerà anche con GNU xargs):

ls -tp | grep -v '/$' | tail -n +6 | tr '\n' '\0' | xargs -0 rm --

Spiegazione:

  • ls -tp stampa i nomi degli elementi del filesystem ordinati in base a quanto recentemente sono stati modificati, in ordine decrescente (prima gli elementi modificati più recentemente) (-t), con le directory stampate con un finale / per contrassegnarli come tali (-p).
  • grep -v '/$' quindi elimina le directory dall'elenco risultante, omettendo (-v) linee che hanno un finale / (/$).
    • Avvertimento:Da collegamento simbolico che punta a una directory tecnicamente non è una directory, tali collegamenti simbolici lo faranno non essere escluso.
  • tail -n +6 salta il primo 5 voci nell'elenco, restituendo in effetti tutti Ma i 5 file modificati più recentemente, se presenti.
    Si noti che per escludere N File, N+1 deve essere passato a tail -n +.
  • xargs -I {} rm -- {} (e le sue variazioni) quindi invoca on rm su tutti questi file;se non ci sono corrispondenze, xargs non farà nulla.
    • xargs -I {} rm -- {} definisce il segnaposto {} che rappresenta ciascuna riga di input nel complesso, COSÌ rm viene quindi richiamato una volta per ogni riga di input, ma con nomi di file con spazi incorporati gestiti correttamente.
    • -- in tutti i casi garantisce che tutti i nomi di file inizino con - non vengono scambiati per opzioni di rm.

UN variazione sul problema originale, nel caso in cui i file corrispondenti debbano essere elaborati individualmente O raccolti in un array di shell:

# One by one, in a shell loop (POSIX-compliant):
ls -tp | grep -v '/$' | tail -n +6 | while IFS= read -r f; do echo "$f"; done

# One by one, but using a Bash process substitution (<(...), 
# so that the variables inside the `while` loop remain in scope:
while IFS= read -r f; do echo "$f"; done < <(ls -tp | grep -v '/$' | tail -n +6)

# Collecting the matches in a Bash *array*:
IFS=$'\n' read -d '' -ra files  < <(ls -tp | grep -v '/$' | tail -n +6)
printf '%s\n' "${files[@]}" # print array elements

Altri suggerimenti

Rimuovi tutti tranne 5 (o qualunque numero) dei file più recenti in una directory.

rm `ls -t | awk 'NR>5'`
(ls -t|head -n 5;ls)|sort|uniq -u|xargs rm

Questa versione supporta nomi con spazi:

(ls -t|head -n 5;ls)|sort|uniq -u|sed -e 's,.*,"&",g'|xargs rm

Variante più semplice della risposta di thelsdj:

ls -tr | head -n -5 | xargs --no-run-if-empty rm 

ls -tr visualizza tutti i file, prima i più vecchi (-t prima i più nuovi, -r inverso).

head -n -5 mostra tutte le righe tranne le ultime 5 (ovvero i 5 file più recenti).

xargs rm chiama rm per ogni file selezionato.

find . -maxdepth 1 -type f -printf '%T@ %p\0' | sort -r -z -n | awk 'BEGIN { RS="\0"; ORS="\0"; FS="" } NR > 5 { sub("^[0-9]*(.[0-9]*)? ", ""); print }' | xargs -0 rm -f

Richiede GNU find per -printf e GNU sort per -z, e GNU awk per "\0" e GNU xargs per -0, ma gestisce file con caratteri di fine riga o spazi incorporati.

Tutte queste risposte falliscono quando sono presenti directory nella directory corrente.Ecco qualcosa che funziona:

find . -maxdepth 1 -type f | xargs -x ls -t | awk 'NR>5' | xargs -L1 rm

Questo:

  1. funziona quando ci sono directory nella directory corrente

  2. tenta di rimuovere ogni file anche se non è stato possibile rimuovere quello precedente (a causa dei permessi, ecc.)

  3. fallisce quando il numero di file nella directory corrente è eccessivo e xargs normalmente ti fregherebbe (il -x)

  4. non prevede spazi nei nomi dei file (forse stai utilizzando il sistema operativo sbagliato?)

ls -tQ | tail -n+4 | xargs rm

Elenca i nomi dei file in base all'ora della modifica, citando ciascun nome di file.Escludi i primi 3 (3 più recenti).Rimuovi rimanente.

EDIT dopo l'utile commento di mklement0 (grazie!):corretto l'argomento -n+3 e nota che non funzionerà come previsto se i nomi dei file contengono ritorni a capo e/o la directory contiene sottodirectory.

Ignorare i ritorni a capo significa ignorare la sicurezza e la buona codifica.wnoise aveva l'unica buona risposta.Ecco una variazione che inserisce i nomi dei file in un array $x

while IFS= read -rd ''; do 
    x+=("${REPLY#* }"); 
done < <(find . -maxdepth 1 -printf '%T@ %p\0' | sort -r -z -n )

Se i nomi dei file non hanno spazi, funzionerà:

ls -C1 -t| awk 'NR>5'|xargs rm

Se i nomi dei file hanno spazi, qualcosa del genere

ls -C1 -t | awk 'NR>5' | sed -e "s/^/rm '/" -e "s/$/'/" | sh

Logica di base:

  • ottieni un elenco dei file in ordine temporale, una colonna
  • ottieni tutti tranne i primi 5 (n=5 per questo esempio)
  • prima versione:mandali a rm
  • seconda versione:gen uno script che li rimuoverà correttamente

Con zsh

Supponendo che non ti interessino le directory presenti e che non avrai più di 999 file (scegli un numero maggiore se lo desideri o crea un ciclo while).

[ 6 -le `ls *(.)|wc -l` ] && rm *(.om[6,999])

In *(.om[6,999]), IL . significa file, il o significa ordinare in ordine, il m significa per data di modifica (put a per l'orario di accesso o c per la modifica dell'inode), il [6,999] sceglie un intervallo di file, quindi non esegue prima i 5.

Mi rendo conto che il thread è vecchio, ma forse qualcuno ne trarrà beneficio.Questo comando troverà i file nella directory corrente:

for F in $(find . -maxdepth 1 -type f -name "*_srv_logs_*.tar.gz" -printf '%T@ %p\n' | sort -r -z -n | tail -n+5 | awk '{ print $2; }'); do rm $F; done

Questo è un po' più efficace rispetto ad alcune delle risposte precedenti in quanto consente di limitare il dominio di ricerca ai file che corrispondono alle espressioni.Per prima cosa, trova i file che corrispondono alle condizioni che desideri.Stampa quei file con i timestamp accanto a loro.

find . -maxdepth 1 -type f -name "*_srv_logs_*.tar.gz" -printf '%T@ %p\n'

Successivamente, ordinali in base ai timestamp:

sort -r -z -n

Quindi, elimina i 4 file più recenti dall'elenco:

tail -n+5

Prendi la seconda colonna (il nome del file, non il timestamp):

awk '{ print $2; }'

E poi racchiudi il tutto in un'istruzione for:

for F in $(); do rm $F; done

Questo potrebbe essere un comando più dettagliato, ma ho avuto molta più fortuna nel riuscire a prendere di mira i file condizionali ed eseguire comandi più complessi su di essi.

ho trovato cmd interessante in Sed-Onliners - Elimina le ultime 3 righe - lo trovo perfetto per un altro modo di scuoiare il gatto (ok, no) ma idea:

 #!/bin/bash
 # sed cmd chng #2 to value file wish to retain

 cd /opt/depot 

 ls -1 MyMintFiles*.zip > BigList
 sed -n -e :a -e '1,2!{P;N;D;};N;ba' BigList > DeList

 for i in `cat DeList` 
 do 
 echo "Deleted $i" 
 rm -f $i  
 #echo "File(s) gonzo " 
 #read junk 
 done 
 exit 0

Rimuove tutti i file tranne i 10 più recenti (i più recenti).

ls -t1 | head -n $(echo $(ls -1 | wc -l) - 10 | bc) | xargs rm

Se meno di 10 file nessun file viene rimosso e avrai:testa dell'errore:conteggio linee illegali -- 0

Per contare i file con bash

Avevo bisogno di una soluzione elegante per busybox (router), tutte le soluzioni xargs o array erano inutili per me: lì non era disponibile alcun comando del genere.find e mtime non sono la risposta corretta poiché stiamo parlando di 10 elementi e non necessariamente di 10 giorni.La risposta di Espo è stata la più breve, chiara e probabilmente la più universale.

Gli errori con gli spazi e quando non è necessario eliminare alcun file vengono entrambi risolti semplicemente nel modo standard:

rm "$(ls -td *.tar | awk 'NR>7')" 2>&-

Versione un po' più educativa:Possiamo fare tutto se usiamo awk in modo diverso.Normalmente, utilizzo questo metodo per passare (ritornare) variabili da awk a sh.Poiché leggiamo continuamente che non è possibile farlo, mi permetto di dissentire:ecco il metodo.

Esempio per file .tar senza problemi relativi agli spazi nel nome del file.Per testare, sostituire "rm" con "ls".

eval $(ls -td *.tar | awk 'NR>7 { print "rm \"" $0 "\""}')

Spiegazione:

ls -td *.tar elenca tutti i file .tar ordinati in base all'ora.Per applicare a tutti i file nella cartella corrente, rimuovere la parte "d *.tar".

awk 'NR>7... salta le prime 7 righe

print "rm \"" $0 "\"" costruisce una linea:rm "nome file"

eval lo esegue

Dal momento che stiamo usando rm, non utilizzerei il comando precedente in uno script!L'uso più saggio è:

(cd /FolderToDeleteWithin && eval $(ls -td *.tar | awk 'NR>7 { print "rm \"" $0 "\""}'))

Nel caso di utilizzo ls -t Il comando non farà alcun danno a esempi così stupidi come: touch 'foo " bar' E touch 'hello * world'.Non che creiamo mai file con tali nomi nella vita reale!

Nota a margine.Se volessimo passare una variabile a sh in questo modo, modificheremmo semplicemente la print (forma semplice, nessuno spazio tollerato):

print "VarName="$1

per impostare la variabile VarName al valore di $1.È possibile creare più variabili in una volta sola.Questo VarName diventa una normale variabile sh e può essere normalmente utilizzata in uno script o in una shell in seguito.Quindi, per creare variabili con awk e restituirle alla shell:

eval $(ls -td *.tar | awk 'NR>7 { print "VarName=\""$1"\""  }'); echo "$VarName"
leaveCount=5
fileCount=$(ls -1 *.log | wc -l)
tailCount=$((fileCount - leaveCount))

# avoid negative tail argument
[[ $tailCount < 0 ]] && tailCount=0

ls -t *.log | tail -$tailCount | xargs rm -f

L'ho trasformato in uno script di shell bash.Utilizzo: keep NUM DIR dove NUM è il numero di file da conservare e DIR è la directory da pulire.

#!/bin/bash
# Keep last N files by date.
# Usage: keep NUMBER DIRECTORY
echo ""
if [ $# -lt 2 ]; then
    echo "Usage: $0 NUMFILES DIR"
    echo "Keep last N newest files."
    exit 1
fi
if [ ! -e $2 ]; then
    echo "ERROR: directory '$1' does not exist"
    exit 1
fi
if [ ! -d $2 ]; then
    echo "ERROR: '$1' is not a directory"
    exit 1
fi
pushd $2 > /dev/null
ls -tp | grep -v '/' | tail -n +"$1" | xargs -I {} rm -- {}
popd > /dev/null
echo "Done. Kept $1 most recent files in $2."
ls $2|wc -l

In esecuzione su Debian (supponiamo che sia lo stesso su altre distribuzioni che ottengo:rm:impossibile rimuovere la directory ".."

il che è abbastanza fastidioso..

Ad ogni modo ho modificato quanto sopra e ho anche aggiunto grep al comando.Nel mio caso ho 6 file di backup in una directory, ad es.file1.tar file2.tar file3.tar ecc e voglio eliminare solo il file più vecchio (rimuovi il primo file nel mio caso)

Lo script che ho eseguito per eliminare il file più vecchio è stato:

ls -c1 -t | File grep | awk 'nr> 5' | xargs rm

Questo (come sopra) elimina il primo dei miei file, ad es.file1.tar anche questo lascia stare con file2 file3 file4 file5 e file6

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top