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> ~/
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> ~/
Risposte:
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
).
a=(*)
Questo crea un array a
con 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.
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
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 cd
la 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.
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 a
viene definita la matrice . Per esempio:
LC_ALL=C a=(*)
Puoi scoprire in quale locale ti trovi attualmente eseguendo il locale
comando.
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 On
ordina 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 cp
comando per i file trattare cui i nomi iniziano con -
correttamente, quindi:
cp -- *(.On[1,4]) ~
Aggiungi il D
qualificatore se vuoi considerare anche i file nascosti (dot-files):
cp -- *(D.On[1,4]) ~
*([-4,-1])
.
on
) è un comportamento predefinito, quindi non è necessario specificarlo, e -
davanti ai numeri conta i nomi dei file dal basso.
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.
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 bash
soluzione di array specifica offerta, ma dovrebbe essere portabile su qualsiasi shell compatibile con POSIX. Alcune note su entrambi i metodi:
È importante condurre i tuoi cp
argomenti 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 cp
primo argomento che può essere interpretato come un'opzione e causare il fallimento dell'operazione o il rendering di risultati non intenzionali.
set -- ./*
o array=(./*)
.È 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' shift
unico succede se ci sono almeno 4 elementi nell'array arg - e poi sposta solo quei primi arg che fanno un surplus di 4 ..
set 1 2 3 4 5
caso si sposterà 1
via, ma in un set 1 2 3
caso non si sposterà nulla.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 sed
sostituzione in stile a tutti i nomi dei file man mano che li copia.
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) ~/
a_
a TUTTI e quattro i file? Ho provato echo "a_$(ls | tail -n 4)"
, ma antepone solo il primo file.
ls
dell'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".
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"
find
vi 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?
LC_ALL=C