Come ottenere gli ultimi N file in una directory?


15

Ho molti file ordinati in base al nome del file in una directory. Vorrei copiare i file N (diciamo, N = 4) finali nella mia directory home. Come dovrei farlo?

cp ./<the final 4 files> ~/


1
Su tutte le risposte di seguito, potrebbe essere necessario aggiungere un "LC_ALL = ..." davanti ai comandi utilizzando gli intervalli, in modo che i loro intervalli utilizzino le impostazioni internazionali giuste per te (le impostazioni locali possono cambiare e l'ordine dei caratteri può cambiare molto. Ex, in alcune versioni locali, [az] può contenere tutte le lettere dell'alfabeto tranne "Z", poiché le lettere sono ordinate: aAbBcC .... zZ. Esistono altre varianti (lettere accentuate e ordinamenti ancora più esotici Una scelta abituale è:LC_ALL=C
Olivier Dulac il

Risposte:


23

Questo può essere fatto facilmente con gli array bash / ksh93 / zsh:

a=(*)
cp -- "${a[@]: -4}" ~/

Funziona con tutti i nomi di file non nascosti anche se contengono spazi, tabulazioni, newline o altri caratteri difficili (supponendo che ci siano almeno 4 file non nascosti nella directory corrente con bash).

Come funziona

  • a=(*)

    Questo crea un array acon tutti i nomi di file. I nomi dei file restituiti da bash sono ordinati alfabeticamente. (Suppongo che questo sia ciò che intendi per "ordinato per nome file" ).

  • ${a[@]: -4}

    Ciò restituisce gli ultimi quattro elementi dell'array a(purché l'array contenga almeno 4 elementi con bash).

  • cp -- "${a[@]: -4}" ~/

    Questo copia gli ultimi quattro nomi di file nella tua home directory.

Per copiare e rinominare allo stesso tempo

Questo copierà gli ultimi quattro file solo nella home directory e, allo stesso tempo, anteporrà la stringa a_al nome del file copiato:

a=(*)
for fname in "${a[@]: -4}"; do cp -- "$fname" ~/a_"$fname"; done

Copia da un'altra directory e rinomina anche

Se usiamo a=(./some_dir/*)invece di a=(*), allora abbiamo il problema della directory allegata al nome del file. Una soluzione è:

a=(./some_dir/*) 
for f in "${a[@]: -4}"; do cp "$f"  ~/a_"${f##*/}"; done

Un'altra soluzione è utilizzare una subshell e cdla directory nella subshell:

(cd ./some_dir && a=(*) && for f in "${a[@]: -4}"; do cp -- "$f" ~/a_"$f"; done) 

Al termine della subshell, la shell ci riporta alla directory originale.

Assicurarsi che l'ordinamento sia coerente

La domanda richiede i file "ordinati per nome file". Tale ordine, sottolinea Olivier Dulac nei commenti, varierà da una località all'altra. Se è importante avere risultati fissi indipendenti dalle impostazioni della macchina, è meglio specificare le impostazioni internazionali in modo esplicito quando aviene definita la matrice . Per esempio:

LC_ALL=C a=(*)

Puoi scoprire in quale locale ti trovi attualmente eseguendo il localecomando.


9

Se si utilizza zshè possibile racchiudere tra parentesi ()un elenco dei cosiddetti qualificatori glob che selezionano i file desiderati. Nel tuo caso, sarebbe

cp *(On[1,4]) ~/

Qui Onordina i nomi dei file in ordine alfabetico in ordine inverso e ne [1,4]accetta solo i primi 4.

È possibile rendere questo più robusto selezionando solo i file semplici (ad esclusione directory, tubi ecc) con ., e anche aggiungendo --al cpcomando per i file trattare cui i nomi iniziano con -correttamente, quindi:

cp -- *(.On[1,4]) ~

Aggiungi il Dqualificatore se vuoi considerare anche i file nascosti (dot-files):

cp -- *(D.On[1,4]) ~

2
Oppure: *([-4,-1]).
Stéphane Chazelas,

2
@ StéphaneChazelas sì, chiariamo per i futuri lettori che l'ordinamento alfabetico in direzione normale ( on) è un comportamento predefinito, quindi non è necessario specificarlo, e -davanti ai numeri conta i nomi dei file dal basso.
Jimmij,

7

Ecco una soluzione che utilizza comandi bash estremamente semplici.

find . -maxdepth 1 -type f | sort | tail -n 4 | while read -r file; do cp "$file" ~/; done

Spiegazione:

find . -maxdepth 1 -type f

Trova tutti i file nella directory corrente.

sort

Ordina in ordine alfabetico.

tail -n 4

Mostra solo le ultime 4 righe.

while read -r file; do cp "$file" ~/; done

Esegue il ciclo su ciascuna riga eseguendo il comando copia.


6

Finché trovi gradevole il tipo di shell, puoi semplicemente fare:

set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
cp "$@" /path/to/target/dir

Questo è molto simile alla bashsoluzione di array specifica offerta, ma dovrebbe essere portabile su qualsiasi shell compatibile con POSIX. Alcune note su entrambi i metodi:

  1. È importante condurre i tuoi cpargomenti con cp --o ottenere uno di .un punto o /all'inizio di ciascun nome. Se non si esegue questa operazione, si rischia un -trattino iniziale nel cpprimo argomento che può essere interpretato come un'opzione e causare il fallimento dell'operazione o il rendering di risultati non intenzionali.

    • Anche se si lavora con la directory corrente come directory di origine, questo può essere fatto facilmente come ... set -- ./*o array=(./*).
  2. È importante quando si utilizzano entrambi i metodi per assicurarsi di avere almeno tanti elementi nell'array arg che si tenta di rimuovere: lo faccio qui con un'espansione matematica. L' shiftunico succede se ci sono almeno 4 elementi nell'array arg - e poi sposta solo quei primi arg che fanno un surplus di 4 ..

    • Quindi in un set 1 2 3 4 5caso si sposterà 1via, ma in un set 1 2 3caso non si sposterà nulla.
    • Ad esempio: a=(1 2 3); echo "${a[@]: -4}"stampa una linea vuota.

Se stai copiando da una directory a un'altra, puoi usare pax:

set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
pax -rws '|.*/|new_prefix|' "$@" /path/to/target/dir

... che applicherebbe una sedsostituzione in stile a tutti i nomi dei file man mano che li copia.


4

Se ci sono solo file e i loro nomi non contengono spazi bianchi o newline (e non hai modificato $IFS) o caratteri glob (o alcuni caratteri non stampabili con alcune implementazioni di ls), e non iniziare con ., allora puoi farlo :

cp -- $(ls | tail -n 4) ~/

Questo si chiama sostituzione di comando ed è davvero utile. tldp.org/LDP/abs/html/commandsub.html#CSPARENS
iyrin

Grazie! Funziona. È possibile anteporre rapidamente un prefisso a_a TUTTI e quattro i file? Ho provato echo "a_$(ls | tail -n 4)", ma antepone solo il primo file.
Sibbs Gioco d'azzardo il

@SibbsGambling Il mio approccio non è adatto a questo, ma la risposta di John1024 può essere facilmente adattata a questo.
Hauke ​​Laging,

5
In generale l'analisi lsdell'output è considerata in cattiva forma perché in genere fallisce in modo orribile sui nomi dei file che contengono non solo spazi, ma anche schede, nuove righe o altri caratteri validi ma "difficili".
HBruijn,

2
@HaukeLaging Anche se al momento non è un problema (perché non ho nomi di nomi "complicati" nella mia directory), potrei averlo tra 2 anni e improvvisamente le cose cadono in piedi ...
glglgl

1

Questa è una semplice soluzione bash senza alcun codice di loop. Copia gli ultimi 4 file dalla directory corrente alla destinazione:

$ find . -maxdepth 1 -type f |tail -n -4|xargs cp -t "$destdir"

1
Come assicurate che findvi dia i file nell'ordine desiderato? Come si fa a garantire che i file contenenti caratteri di spazi bianchi (comprese le nuove righe) nei loro nomi siano gestiti correttamente?
Kusalananda
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.