Copia il gruppo di file (Nome file *) nel backup (Nome file * .bak)


13

sfondo

In Linux puoi:

  • Elencare un gruppo di file con ls Filename*
  • Rimuovere un gruppo di file con rm Filename*
  • Sposta un gruppo di file con mv Filename* /New/Directory
  • Ma non puoi copiare un gruppo di file con:cp Filename* *.bak

Modifica il cpcomando Linux per copiare un gruppo di file

Ho un gruppo di file che vorrei copiare senza inserire i nomi uno per uno e usando il cpcomando:

$ ls gmail-meta3*
gmail-meta3                          gmail-meta3-REC-1558392194-26467821
gmail-meta3-LAB-1558392194-26467821  gmail-meta3-YAD-1558392194-26467821

Come posso usare qualcosa come il vecchio comando DOS copy gmail-meta3* *.bak?

Non voglio digitare un comando simile quattro volte:

cp gmail-meta3-LAB-1558392194-26467821 gmail-meta3-LAB-1558392194-26467821.bak

Sto cercando uno script / funzione / app che accetti i parametri per il vecchio e il nuovo gruppo di nomi di file e non qualcosa con nomi di file codificati. Ad esempio, un utente può digitare:

copy gmail-meta3* *.bak

o potrebbero digitare:

copy gmail-meta3* save-*

1
Il problema mi sembra usare l'operatore glob due volte che nessuno degli altri tuoi comandi usa. bash non è abbastanza intelligente da gestirlo.
qwr,

2
@qwr, il fatto che bash espanda i metacaratteri e tokenizza l'input prima di consegnarlo a un comando da eseguire è parte del design delle shell UNIX. Provare in qualche modo a programmare un'eccezione per il comando cp interromperebbe l'intera coerenza di bash, che non sarebbe affatto intelligente. Come esercizio, cerca di capire cosa sta succedendo qui, e perché è l'espansione metacaratterica della shell che lo rende così:touch aa ab ba; mkdir bb; cp a* b*; ls *
Mike S

@MikeS Grazie per i suggerimenti. Ieri qualcun altro ha detto che puoi usare un carattere jolly *per i nomi dei file di origine ma non per i nomi dei file di destinazione. Come jolly sostitutivo (credo sia ##stato suggerito ma mi sto sporgendo verso %) dovrà essere usato per il bersaglio. Penso che questo sia ciò che stai rafforzando? Non mi aspettavo di cambiare il cpcomando. Basta creare uno script wrapper chiamato copyche emulava (entro limiti ragionevoli) il comando di copia DOS.
WinEunuuchs2Unix il

@ WinEunuuchs2Unix quella persona aveva ragione. I metacaratteri della shell sono indipendenti dai comandi. Quindi tutti i caratteri jolly proveranno ad abbinare tutti i file che corrispondono allo schema. Se stai provando a creare un "scopo generale" per abbinare tutto e copiarlo in qualunque cosa fossero, ma aggiungi questo suffisso ", allora sì, inserendo un metacarattere senza caratteri di escape come target probabilmente non farà ciò che desideri. Perché tutti i metacaratteri nella riga di comando della shell vengono espansi. Se SAPETE per certo che il metacarattere target non formerà mai una corrispondenza, potreste usarlo perché la shell non può espanderlo.
Mike S,

... ma sarebbe una brutta cosa da fare. Meglio usare un personaggio speciale. % o carattere di sottolineatura sono buoni, generalmente non sono metacaratteri (ma fai attenzione a usare% in un file crontab; qui è speciale).
Mike S,

Risposte:


14

Ecco un esempio di un utilizzo atipico di sed, applicabile per questa attività:

sed -i.bak '' file-prefix*

In questo modo, in realtà, sednon cambierà i file, perché non abbiamo fornito alcun comando '', ma grazie all'opzione -i[suffix]creerà una copia di backup di ogni file. Ho trovato questo approccio durante la ricerca Esiste un modo per creare una copia di backup di un file, senza digitarne il nome due volte?


FYI: $ time sed -i.bak '' gmail-meta3*=real 0m0.069s
WinEunuuchs2Unix

Se i file già esiste, allora: real 0m0.037s. Se i file cancellati ed eseguire una seconda volta vicino alla cpvelocità: real 0m0.051s.
WinEunuuchs2Unix il

@xiota Un punto interessante. Risulta sedpiù veloce quando esistono già file target ma cpè più lento quando esistono file target. Effettivamente svuoto cache e buffer anziché syncquando eseguo grandi test di temporizzazione, ma questa volta non l'ho fatto. Dato che questo può divagare in una soap opera completamente fuori tema, mi pento di condividere i risultati del mio test :( È troppo tardi per fingere che questa conversazione non sia mai avvenuta? La dimensione dei metadati del messaggio GY di FYI è di 2,5 MB e 3 file di indice sono di circa 800 KB Inoltre non è un disco rigido, è un SSD Samsung Pro 960 NVMe su 4 canali
WinEunuuchs2Unix

1
Probabilmente non importa quale dispositivo di archiviazione hai per questi test, se ciò accade su una macchina Linux. Il kernel è estremamente bravo nel buffering dei file in memoria. Questo è il valore "buff / cache" quando si utilizza il freecomando. Le scritture effettive sul dispositivo avvengono in un momento scelto da un algoritmo che tiene conto dell'età della cache e della pressione della memoria sulla macchina. Se stai provando più test, le prime letture dei file verranno rimosse dal disco ma le letture successive molto probabilmente usciranno dalla memoria (vedi sync; echo 3 > /proc/sys/vm/drop_caches).
Mike S,

Ieri ho fatto alcuni test con file di grandi dimensioni oltre 2 GB e - sì, questo approccio è relativamente lento rispetto all'utilizzo del cpcomando, ma non posso dire che ci sia una differenza significativa nelle prestazioni .
pa4080,

13

Puoi usare find:

find . -max-depth 1 -name 'gmail-meta3*' -exec cp "{}" "{}.bak" \;

Che troverà nella directory corrente .tutti i file con un nome corrispondente al pattern glob (attenzione alle virgolette singole attorno al pattern per evitare il gorgogliamento della shell). Per ogni file trovato, verrà cpeseguito dal nome al nome.bak. Il \; alla fine si assicura che farà ogni singolo file invece di passarli tutti in una volta. La profondità massima come 1 cerca solo nella directory corrente anziché ricorrere verso il basso.


1
Funzionerà con find . -max-depth 1 -name '"$1"'' -exec cp "{}" "{}$2" \;quando $ 1 è sorgente e $ 2 è estensione?
WinEunuuchs2Unix il

$ 2 dovrebbe essere ok per sostituire fino a quando è ragionevole. $ 1 potrebbe essere più complicato in quanto non è possibile effettuare la sostituzione variabile tra virgolette singole. Non ne sono sicuro, ma potrebbe essere possibile usare $ 1 tra virgolette poiché il modello è memorizzato in una stringa.
cbojar,

11

Puoi usare un forloop conbash . Normalmente, lo scriverei solo come una riga, perché questo non è un compito che eseguo spesso:

for f in test* ; do cp -a "$f" "prefix-${f}.ext" ; done

Tuttavia, se ne hai bisogno come script:

cps() {
   [ $# -lt 2 ] && echo "Usage: cps REGEXP FILES..." && return 1

   PATTERN="$1" ; shift

   for file in "$@" ; do
      file_dirname=`dirname "$file"`
      file_name=`basename "$file"`
      file_newname=`echo "$file_name" | sed "$PATTERN"`

      if [[ -f "$file" ]] && [[ ! -e "${file_dirname}/${file_newname}" ]] ; then
         cp -a "$file" "${file_dirname}/${file_newname}"
      else
         echo "Error: $file -> ${file_dirname}/${file_newname}"
      fi
   done
}

L'utilizzo è simile a rename. Testare:

pushd /tmp
mkdir tmp2
touch tmp2/test{001..100}     # create test files
ls tmp2
cps 's@^@prefix-@ ; s@$@.bak@' tmp2/test*    # create backups
cps 's@$@.bak@' tmp2/test*    # more backups ... will display errors
ls tmp2
\rm -r tmp2                   # cleanup
popd

FYI: $ time for f in gmail-meta3* ; do cp -a "$f" "${f}.bak" ; done=real 0m0.046s
WinEunuuchs2Unix

Ehm no, non voglio ottimizzare per tempo. Sono i 0.046secondi che significano 0 secondi per la percezione umana. Stavo solo cercando di mostrare come stavo testando le risposte postate e passando curiosità interessanti agli spettatori che hanno guardato il sedcomando sopra. O almeno ero interessato a confrontarmi sedcon cp....
WinEunuuchs2Unix il

Tuttavia, la tua soluzione cpè più veloce della soluzione sed. Quindi è un motivo di festa :)
WinEunuuchs2Unix il

(1)  -aè un operatore di test non standard. Perché non usare -e? (2) "Impossibile creare la directory temporanea." è un messaggio di errore in qualche modo fuorviante. (3) Perché non usarlo mktemp -d? (4) È necessario testare gli stati di uscita. Ad esempio, dovresti dire ! mkdir "$FOLDER" && echo "Unable to create temporary directory." && return 1 o  mkdir "$FOLDER" || { echo "Unable to create temporary directory."; return 1;}. Allo stesso modo per cpe  rename(e forse anche pushd, se vuoi stare attento). ... (proseguendo)
G-Man dice "Ripristina Monica" il

(Proseguendo) ... (5) Arrggghhhh! Non dire $@; dire "$@". (5b)  Non è necessario utilizzare {e } quando si fa riferimento alle variabili nel modo in cui si sta eseguendo ( "${FOLDER}",  "${PATTERN}" e  "${file}"); basta fare "$FOLDER",  "$PATTERN" e  "$file". (6) Ciò presuppone che i file si trovino nella directory corrente.  cps 's/$/.bak/' d/foocopierà d/fooa foo.bak nella directory corrente, non è d/foo.bak.
G-Man dice "Ripristina Monica" il

6

Il più vicino probabilmente al paradigma DOS è mcp(dal mmvpacchetto):

mcp 'gmail-meta3*' 'gmail-meta3#1.bak'

Se zshè disponibile, il suo zmvmodulo contribuito è forse un po 'più vicino:

autoload -U zmv

zmv -C '(gmail-meta3*)' '$1.bak'

Eviterei a lsprescindere - sarebbe una variante della tua risposta che è sicura per gli spazi bianchi (comprese le nuove righe)

printf '%s\0' gmail-meta3* | while IFS= read -r -d '' f; do cp -a -- "$f" "$f.bak"; done

o forse

printf '%s\0' gmail-meta3* | xargs -0 -I{} cp -a -- {} {}.bak

Capisco mmvè il pacchetto ma nei commenti dici che il comando è mcpma poi nel comando usi mmvche è anche un comando nel mmvpacchetto. Mi piace la direzione degli printfesempi e in uno script raffinato assicurerei che $ 1 e $ 2 siano passati. +1 per far rotolare la palla :)
WinEunuuchs2Unix il

@ WinEunuuchs2Unix si scusa - il mcp / mmv è stato un brainfart. In realtà mcpè solo un sinonimo dimmv -c
steeldriver il

Non preoccuparti. Se avessi un dollaro per ogni errore di battitura che ho fatto, sarei un milionario :) Vorrei chiarimenti sul printfcomando che non ho mai usato davvero. Stai dicendo printf '%s\0' "$1"*che funzionerebbe se gmail-meta3fosse passato come parametro 1?
WinEunuuchs2Unix il

@ WinEunuuchs2Unix Probabilmente avrei lasciato che il contesto chiamante facesse il globbing, cps gmail-meta3*e quindi scrivessi printf '%s\0"$ @" | mentre ... `nella funzione. O semplicemente usa for f; do cp -- "$f" "$f.bak"; done(come la risposta di Xiota , ma come una funzione)
steeldriver il

1
Da notare che con zmvte puoi usare la modalità "sostituzione jolly", che trovo un po 'più facile da individuare:zmv -W -C 'gmail-meta3*' '*.bak'
0x5453

5

unica soluzione rsync

Se si desidera solo eseguire il backup dei file, è possibile copiarli in una nuova directory

rsync /path/to/dir/Filename* /path/to/backupdirectory

Questo copierà i Filenamefile da /path/to/dir/a /path/to/backupdirectory.


rsync + nome file

Se vuoi che i tuoi file di backup abbiano un suffisso, le cose si fanno confuse con rsync...

rsync -Iu /path/to/dir/Filename* /path/to/dir/Filename* -b --backup-dir=/path/to/backupdirectory --suffix=.bak

Ciò sovrascriverebbe i file esistenti ... con i file esistenti ( -I) ma solo se sono ( -u) più recenti (cosa che non sono) e creando un backup, con un suffisso.

Puoi anche farlo nella stessa directory. Ma meglio escludere i backup esistenti.

rsync -Iu /path/to/dir/Filename* /path/to/dir/Filename* -b --backup-dir=/path/to/backupdirectory --suffix=.bak --exclude '*.bak'


Adoro rsycncquindi ho effettuato l'upgrade ma, un metodo più semplice sarebbe cp Filename* /path/to/backup/dirperché i file non avrebbero bisogno *.bakdell'unicificatore se fossero in una directory separata.
WinEunuuchs2Unix,

4

Questo dovrebbe fare come richiesto:

cps(){ p="${@: -1}"; for f in "${@:1:$#-1}"; do cp -ai "$f" "${p//\?/$f}"; done  }

Uso:

cps FILES... pattern
Example 1: cps gmail-meta3* ?.bak
Example 2: cps * save-?
Example 3: cps * bla-?-blubb

Ho scelto ?perché #deve essere citato quando è il primo carattere del modello, altrimenti è riconosciuto come l'inizio di un commento.

Test:

$ touch 'test};{bla#?"blubb'
$ cps test* bla-?-blubb
$ ls
test};{bla#?"blubb  bla-test};{bla#?"blubb-blubb


Alcune versioni precedenti dello script per l'aggiunta di un suffisso:

Simile alla risposta @ WinEunuuchs2Unix, ma penso che sia più flessibile e non analizzals :

cps(){ S="$1"; shift; printf '%s\0' "$@" | xargs -0 -I{} cp -abfS "$S" {} {}; }

Metti questo nel tuo .bashrc.

Uso:

cps SUFFIX FILES...
Example: cps .bak gmail-meta3*

Alternativa, con il suffisso come ultimo argomento ( via e via ):

cps(){ S="${@: -1}"; printf '%s\0' "${@:1:$#-1}" | xargs -0 -I{} cp -abfS "$S" {} {}; }

Uso:

cps FILES... SUFFIX
Example: cps gmail-meta3* .bak


Buona codifica, ma è difficile dopo decenni di utilizzo di Source e Target per cambiare il comando di copia su Target e poi su Source
WinEunuuchs2Unix

Aggiunta la funzione con il suffisso nella parte posteriore.
pLumo,

Grazie è più intuitivo. Chiamarlo suffisso è preciso nel modo in cui la mia risposta lo ha codificato, ma è davvero un obiettivo o una destinazione. Altri utenti potrebbero voler utilizzare: copy gmail-meta3* old-meta3*. Nella mia risposta non sono riuscito a capire come *inserire il nome di destinazione come richiesto dalla mia domanda ...
WinEunuuchs2Unix

il problema è che *viene interpretato dalla shell, quindi la funzione non lo saprà. Avresti bisogno di qualche altro carattere o citalo, quindi sostituiscilo con il nome file originale all'interno della funzione.
pLumo,

Immagino che #potrebbe essere usato come jolly sostitutivo per *? Quindi potresti digitare copy filenames# save-#. Penso che vorresti che il carattere jolly fosse lo stesso per sorgente e destinazione.
WinEunuuchs2Unix il

4

Ho scritto questo one-liner nel mio ~/.bashrc. findSuppongo che si possano pubblicare risposte molto migliori usando . Potrebbero essere scritte anche risposte migliori in C. Speriamo che queste domande e risposte facciano rotolare la palla per risposte migliori:

cps () {
    # cps "Copy Splat", copy group of files to backup, ie "cps Filename .bak"
    # Copies Filename1 to Filename1.bak, Filename2 to Filename2.bak, etc.
    # If Filename1.bak exists, don't copy it to Filename1.bak.bak
    for f in "$1"*; do [[ ! "$f" == *"$2" ]] && cp -a "$f" "$f$2"; done

    # OLD version comments suggested to remove 
    # ls "$1"* | while read varname; do cp -a "$varname" "$varname$2"; done
}
  • for f in "$1"*; do: $1è il gmail-meta3parametro ed fè l'elenco dei file corrispondenti. In combinazione, ciò significa che per gmail-meta3, gmail-meta3-LAB-9999, ecc. Procedere come segue
  • [[ ! "$f" == *"$2" ]] &&: $fè lo stesso di cui fsopra. $2è il .bakparametro passato. Combinato significa che se il nome file non termina .bak(perché non vogliamo copiare .bake creare .bak.bak), procedere come segue
  • cp -a "$f" "$f$2"; copia gmail-meta3 in gmail-meta3.bak, ecc.
  • done: torna indietro e prendi il nome del file successivo gmail-meta3nell'elenco *.

cps gmail-meta3 .bak Uscita campione

Usando la domanda come esempio qui è come appare in azione:

───────────────────────────────────────────────────────────────────────────────────────────
rick@alien:~/gmail$ ll gmail-meta3*
-rw-rw-r-- 1 rick rick 26467821 May 20 16:43 gmail-meta3
-rw-rw-r-- 1 rick rick 26467821 May 20 16:43 gmail-meta3.bak
-rw-rw-r-- 1 rick rick      643 May 20 16:43 gmail-meta3-LAB-1558392194-26467821
-rw-rw-r-- 1 rick rick      643 May 20 16:43 gmail-meta3-LAB-1558392194-26467821.bak
-rw-rw-r-- 1 rick rick    49607 May 20 16:44 gmail-meta3-REC-1558392194-26467821
-rw-rw-r-- 1 rick rick    49607 May 20 16:44 gmail-meta3-REC-1558392194-26467821.bak
-rw-rw-r-- 1 rick rick   728954 Jun 27 17:04 gmail-meta3-YAD-1558392194-26467821
-rw-rw-r-- 1 rick rick   728954 Jun 27 05:46 gmail-meta3-YAD-1558392194-26467821.bak
───────────────────────────────────────────────────────────────────────────────────────────
rick@alien:~/gmail$ cps gmail-meta3 .bak
───────────────────────────────────────────────────────────────────────────────────────────
rick@alien:~/gmail$ ll gmail-meta3*
-rw-rw-r-- 1 rick rick 26467821 May 20 16:43 gmail-meta3
-rw-rw-r-- 1 rick rick 26467821 May 20 16:43 gmail-meta3.bak
-rw-rw-r-- 1 rick rick      643 May 20 16:43 gmail-meta3-LAB-1558392194-26467821
-rw-rw-r-- 1 rick rick      643 May 20 16:43 gmail-meta3-LAB-1558392194-26467821.bak
-rw-rw-r-- 1 rick rick    49607 May 20 16:44 gmail-meta3-REC-1558392194-26467821
-rw-rw-r-- 1 rick rick    49607 May 20 16:44 gmail-meta3-REC-1558392194-26467821.bak
-rw-rw-r-- 1 rick rick   728954 Jun 27 17:04 gmail-meta3-YAD-1558392194-26467821
-rw-rw-r-- 1 rick rick   728954 Jun 27 17:04 gmail-meta3-YAD-1558392194-26467821.bak
───────────────────────────────────────────────────────────────────────────────────────────
rick@alien:~/gmail$ 

Nota: utilizza il -aflag con il cpcomando per conservare i timestamp e offrire una migliore comprensione dei backup dei file.

Notare come le copie dei file abbiano la stessa data e ora esatte degli originali. Se il -aparametro fosse omesso, verrebbero forniti la data e l'ora correnti e non sembrerebbe un vero backup, tranne per il fatto che la dimensione del file sarebbe la stessa.


6
le persone non sconsigliano sempre di analizzarels
qwr il

3
Dato che dici findche presumo tu sia consapevole dei pericoli dell'analisi ls? Ma nel tuo caso nessuno dei due è necessario: basta farlo for file in "$1"*; do copy -a "$file" "$file$2"; done: questo è completamente sicuro e molto più semplice di qualsiasi tipo di indiretto tramite lso finde un whileciclo.
Konrad Rudolph,

@KonradRudolph Grazie per il tuo suggerimento. Ho implementato e testato il tuo suggerimento con un paio di modifiche minori.
WinEunuuchs2Unix,

2

Un altro metodo per raggiungere il requisito è copiare i file in una directory temporanea e utilizzare il renamecomando per rinominarli.

$ mkdir backup
$ cp filename* /tmp/rename-backup/
$ rename 's/(filename.*)/$1.bak/' /tmp/rename-backup/*
$ mv /tmp/rename-backup/* ./

Se ne hai bisogno come script, puoi usarlo in questo modo

cps () {
    mkdir -p /tmp/rename-backup/
    cp "$1"* /tmp/rename-backup/
    rename "s/($1.*)/\$1.$2/" /tmp/rename-backup/*
    mv "/tmp/rename-backup/$1"*".$2" .
}

E puoi usarlo in questo modo:

cps file bak

Questo è un esempio

$ ls -l
total 0
-rw-r--r--  1 danny  danny  0 Jun 26 16:23 file a
-rw-r--r--  1 danny  danny  0 Jun 26 16:23 file ab
-rw-r--r--  1 danny  danny  0 Jun 26 16:23 file ac
-rw-r--r--  1 danny  danny  0 Jun 26 16:05 filename1
-rw-r--r--  1 danny  danny  0 Jun 26 16:05 filename2
-rw-r--r--  1 danny  danny  0 Jun 26 16:05 filename3
-rw-r--r--  1 danny  danny  0 Jun 26 16:05 filename4
$ cps file bak
$ ls -l
total 0
-rw-r--r--  1 danny  danny  0 Jun 26 16:23 file a
-rw-r--r--  1 danny  danny  0 Jun 26 16:41 file a.bak
-rw-r--r--  1 danny  danny  0 Jun 26 16:23 file ab
-rw-r--r--  1 danny  danny  0 Jun 26 16:41 file ab.bak
-rw-r--r--  1 danny  danny  0 Jun 26 16:23 file ac
-rw-r--r--  1 danny  danny  0 Jun 26 16:41 file ac.bak
-rw-r--r--  1 danny  danny  0 Jun 26 16:05 filename1
-rw-r--r--  1 danny  danny  0 Jun 26 16:41 filename1.bak
-rw-r--r--  1 danny  danny  0 Jun 26 16:05 filename2
-rw-r--r--  1 danny  danny  0 Jun 26 16:41 filename2.bak
-rw-r--r--  1 danny  danny  0 Jun 26 16:05 filename3
-rw-r--r--  1 danny  wheel  0 Jun 26 16:41 filename3.bak
-rw-r--r--  1 danny  danny  0 Jun 26 16:05 filename4
-rw-r--r--  1 danny  danny  0 Jun 26 16:41 filename4.bak
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.