Copiez le groupe de fichiers (Nom de fichier *) vers la sauvegarde (Nom de fichier * .bak)


13

Contexte

Sous Linux, vous pouvez:

  • Liste un groupe de fichiers avec ls Filename*
  • Supprimer un groupe de fichiers avec rm Filename*
  • Déplacer un groupe de fichiers avec mv Filename* /New/Directory
  • Mais vous ne pouvez pas copier un groupe de fichiers avec:cp Filename* *.bak

Modifier la cpcommande Linux pour copier un groupe de fichiers

J'ai un groupe de fichiers que je voudrais copier sans entrer les noms un par un et en utilisant la cpcommande:

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

Comment puis-je utiliser quelque chose comme l'ancienne commande DOS copy gmail-meta3* *.bak?

Je ne veux pas taper quatre fois une commande similaire:

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

Je cherche un script / une fonction / une application qui accepte des paramètres pour l'ancien et le nouveau groupe de noms de fichiers et non quelque chose avec des noms de fichiers codés en dur. Par exemple, un utilisateur peut taper:

copy gmail-meta3* *.bak

ou ils pourraient taper:

copy gmail-meta3* save-*

1
Le problème me semble en utilisant l'opérateur glob deux fois qu'aucune de vos autres commandes n'utilise. bash n'est pas assez intelligent pour le gérer.
qwr

2
@qwr, le fait que bash développe des métacaractères et symbolise l'entrée avant de la transmettre à une commande à exécuter fait partie de la conception des shells UNIX. En quelque sorte, essayer de programmer une exception pour la commande cp casserait la cohérence entière de bash, ce qui ne serait pas intelligent du tout. En tant qu'exercice, essayez de comprendre ce qui se passe ici et pourquoi c'est l'expansion des métacaractères du shell qui le rend:touch aa ab ba; mkdir bb; cp a* b*; ls *
Mike S

@MikeS Merci pour les pointeurs. Hier, quelqu'un d'autre a dit que vous pouvez utiliser un caractère générique *pour les noms de fichiers source mais pas pour les noms de fichiers cibles. En tant que tel, le caractère générique de remplacement (je pense que cela a ##été suggéré mais je me penche vers %) devra être utilisé pour la cible. Je pense que c'est ce que vous renforcez? Je ne m'attendais pas du tout à changer la cpcommande. Créez simplement un script wrapper appelé copyqui émule (dans des limites raisonnables) la commande de copie DOS.
WinEunuuchs2Unix

@ WinEunuuchs2Unix cette personne était correcte. Les métacaractères du shell sont indépendants des commandes. Ainsi, tous les caractères génériques essaieront de faire correspondre tous les fichiers qui correspondent au modèle. Si vous essayez de faire un usage général « tout match et les copier à ce qu'ils étaient , mais ajouter ce suffixe » programme, alors oui, mettre non échappés métacaractère comme la cible ne sera probablement pas faire ce que vous voulez. Parce que tous les métacaractères de la ligne de commande du shell sont développés. Si vous SAVEZ avec certitude que le métacaractère cible ne formera jamais de correspondance, vous pouvez l'utiliser - car le shell ne peut pas le développer.
Mike S

... mais ce serait une chose laide à faire. Mieux vaut utiliser un caractère spécial. % ou souligné sont bons, ils ne sont généralement pas des métacaractères (mais attention à utiliser% dans un fichier crontab; c'est spécial là-bas).
Mike S

Réponses:


14

Voici un exemple d'utilisation atypique de sed, applicable à cette tâche:

sed -i.bak '' file-prefix*

De cette façon, en fait, sedne changera pas les fichiers, car nous n'avons fourni aucune commande '', mais en raison de l'option, -i[suffix]il créera une copie de sauvegarde de chaque fichier. J'ai trouvé cette approche lorsque je cherchais. Existe-t-il un moyen de créer une copie de sauvegarde d'un fichier sans taper deux fois son nom?


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

Si les fichiers existent déjà alors: real 0m0.037s. Si les fichiers supprimés et exécuter une seconde fois près de la cpvitesse: real 0m0.051s.
WinEunuuchs2Unix

@xiota Un point intéressant. S'avère sedest plus rapide lorsque les fichiers cibles existent déjà, mais cpest plus lent lorsque les fichiers cibles existent. En fait, je vide les caches et les tampons plutôt que synclors de grands tests de synchronisation, mais je n'ai rien fait cette fois-ci. Comme cela peut s'égarer dans un tout autre feuilleton hors sujet, je regrette de partager mes résultats de test :( Est-il trop tard pour prétendre que cette conversation n'a jamais eu lieu? Ce n'est pas non plus un disque dur, c'est un SSD Samsung Pro 960 NVMe sur 4 canaux
WinEunuuchs2Unix

1
Peu importe le périphérique de stockage dont vous disposez pour ces tests, si cela se produit sur une machine Linux. Le noyau est extrêmement bon pour mettre les fichiers en mémoire tampon. C'est ce qu'est la valeur "buff / cache" lors de l'utilisation de la freecommande. Les écritures réelles sur l'appareil ont lieu à un moment choisi par un algorithme qui prend en compte l'âge du cache et la pression de la mémoire sur la machine. Si vous essayez plusieurs tests, les premières lectures de fichiers seront supprimées du disque, mais les lectures suivantes proviendront très probablement directement de la mémoire (voir sync; echo 3 > /proc/sys/vm/drop_caches).
Mike S

Hier, j'ai fait quelques tests avec des fichiers volumineux de plus de 2 Go et - oui, cette approche est relativement lente que l'utilisation de la cpcommande, mais je ne peux pas dire qu'il existe une différence de performances significative .
pa4080

13

Vous pouvez utiliser find:

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

Cela trouvera dans le répertoire courant .tous les fichiers avec un nom correspondant au modèle glob (faites attention aux guillemets simples autour du modèle pour éviter le globbing du shell). Pour chaque fichier trouvé, il cps'exécutera de nom en nom.bak. Le \; à la fin, il s'assurera qu'il exécutera chaque fichier individuellement au lieu de les transmettre tous en même temps. La profondeur maximale de 1 ne fait que rechercher dans le répertoire cuurent au lieu de revenir en arrière.


1
Cela fonctionnera-t-il find . -max-depth 1 -name '"$1"'' -exec cp "{}" "{}$2" \;lorsque $ 1 est source et $ 2 est extension?
WinEunuuchs2Unix

2 $ devraient être autorisés à remplacer tant qu'ils sont raisonnables. $ 1 pourrait être plus délicat car nous ne pouvons pas faire de substitution de variable entre guillemets simples. Je ne suis pas sûr au départ, mais il pourrait être possible d'utiliser $ 1 entre guillemets car le modèle est stocké dans une chaîne.
cbojar

11

Vous pouvez utiliser une forboucle avecbash . Normalement, je le taperais simplement en une ligne car ce n'est pas une tâche que j'exécute souvent:

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

Cependant, si vous en avez besoin en tant que 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'utilisation est similaire à rename. Tester:

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

Non, je ne veux pas optimiser le temps. Il s'agit de 0.046secondes, ce qui signifie 0 seconde pour la perception humaine. J'essayais simplement de montrer comment je testais les réponses publiées et de transmettre des informations intéressantes aux téléspectateurs qui ont regardé la sedcommande ci-dessus. Ou du moins, je voulais comparer sedavec cp...
WinEunuuchs2Unix

Cependant, votre solution avec cpest plus rapide que la solution avec sed. C'est donc un motif de fête :)
WinEunuuchs2Unix

(1)  -aest un opérateur d'essai non standard. Pourquoi ne pas utiliser -e? (2) «Impossible de créer un répertoire temporaire.» est un message d'erreur quelque peu trompeur. (3) Pourquoi ne pas simplement utiliser mktemp -d? (4) Vous devez tester les états de sortie. Par exemple, vous devez dire ! mkdir "$FOLDER" && echo "Unable to create temporary directory." && return 1 ou  mkdir "$FOLDER" || { echo "Unable to create temporary directory."; return 1;}. De même pour cpet  rename(et peut-être même pushd, si vous voulez faire attention). … (Suite)
G-Man dit «Réintègre Monica» le

(Suite)… (5) Arrggghhhh! Ne dites pas $@; dire "$@". (5b)  Il n'est pas nécessaire d'utiliser {et } lorsque vous référencez des variables comme vous le faites ( "${FOLDER}",  "${PATTERN}" et  "${file}"); fais juste "$FOLDER",  "$PATTERN" et  "$file". (6) Cela suppose que les fichiers se trouvent dans le répertoire courant.  cps 's/$/.bak/' d/foocopiera d/fooà foo.bak dans le répertoire courant, non d/foo.bak.
G-Man dit `` Réintègre Monica '' le

6

Le plus proche que vous obtiendrez probablement du paradigme DOS est mcp(à partir du mmvpackage):

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

S'il zshest disponible, son zmvmodule contribué est peut-être un peu plus proche:

autoload -U zmv

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

J'éviterais lsmalgré tout - une variante de votre propre réponse qui est sans danger pour les espaces blancs (y compris les nouvelles lignes) serait

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

ou peut-être

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

Je comprends que mmvc'est le package, mais dans les commentaires, vous dites que la commande l'est, mcpmais dans la commande que vous utilisez, mmvqui est également une commande dans le mmvpackage. J'aime la direction des printfexemples et dans un script soigné je m'assurerais que $ 1 et $ 2 ont été passés. +1 pour faire rouler la balle :)
WinEunuuchs2Unix

@ WinEunuuchs2Unix excuses - le mcp / mmv était un brainfart. En fait, mcpest juste un synonyme demmv -c
Steeldriver

Pfft pas de soucis. Si j'avais un dollar pour chaque faute de frappe que je ferais, je serais millionnaire :) J'aimerais des précisions sur une printfcommande que je n'ai jamais vraiment utilisée. Êtes-vous en train de dire que printf '%s\0' "$1"*cela fonctionnerait s'il gmail-meta3était passé en tant que paramètre 1?
WinEunuuchs2Unix

@ WinEunuuchs2Unix Je laisserais probablement le contexte appelant faire le globbing ie cps gmail-meta3*puis j'écrirais printf '%s\0"$ @" | tandis que ... `dans la fonction. Ou utilisez simplement for f; do cp -- "$f" "$f.bak"; done(comme la réponse de Xiota , mais en fonction)
Steeldriver

1
Notez qu'avec zmvvous, vous pouvez utiliser le mode "remplacement de caractères génériques", que je trouve un peu plus facile à maîtriser:zmv -W -C 'gmail-meta3*' '*.bak'
0x5453

5

solution rsync uniquement

Si vous souhaitez simplement sauvegarder vos fichiers, vous pouvez les copier dans un nouveau répertoire

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

Cela copiera les Filenamefichiers de /path/to/dir/à /path/to/backupdirectory.


rsync + filerename

Si vous voulez que vos fichiers de sauvegarde aient un suffixe, les choses deviennent difficiles avec rsync...

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

Cela écraserait les fichiers existants ... avec les fichiers existants ( -I) mais seulement s'ils sont ( -u) plus récents (ce qu'ils ne sont pas) et en créant une sauvegarde, avec un suffixe.

Vous pouvez également le faire dans le même répertoire. Mais il vaut mieux exclure les sauvegardes existantes.

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


J'adore rsycncdonc j'ai voté positivement mais, une méthode plus simple serait cp Filename* /path/to/backup/dirparce que les fichiers n'auraient pas besoin d' *.bakuniquificateur s'ils étaient dans un répertoire séparé.
WinEunuuchs2Unix

4

Celui-ci devrait faire comme demandé:

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

Usage:

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

J'ai choisi ?car #il faut le citer quand c'est le premier caractère du motif, sinon il est reconnu comme début d'un commentaire.

Tester:

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


Quelques versions antérieures du script pour ajouter un suffixe:

Semblable à la réponse @ WinEunuuchs2Unix, mais je pense que plus flexible et ne pas analyserls :

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

Mettez ceci dans votre .bashrc.

Usage:

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

Alternative, avec le suffixe comme dernier argument ( via et via ):

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

Usage:

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


Bon codage mais c'est un peu difficile après des décennies d'utilisation de Source puis de Target pour basculer la commande de copie vers Target puis Source
WinEunuuchs2Unix

Ajout de la fonction avec le suffixe à l'arrière.
pLumo

Merci c'est plus intuitif. L'appeler un suffixe est précis comme le codait ma réponse, mais c'est vraiment une cible ou une destination. D' autres utilisateurs pourraient vouloir utiliser: copy gmail-meta3* old-meta3*. Dans ma réponse, je ne pouvais pas comprendre comment entrer *dans le nom de destination comme ma question l'avait demandé ...
WinEunuuchs2Unix

problème est *interprété par le shell, donc la fonction ne le saura pas. Il vous faudrait un autre caractère ou le citer, puis le remplacer par le nom de fichier d'origine dans la fonction.
pLumo

Je suppose que le #pourrait être utilisé comme remplacement pour *? Vous pouvez donc taper copy filenames# save-#. Je pense que vous voudriez que le caractère générique soit le même pour la source et la cible.
WinEunuuchs2Unix

4

J'ai écrit ce one-liner dans mon ~/.bashrc. findJe suppose que de bien meilleures réponses en utilisant peuvent être affichées. Des réponses encore meilleures pourraient être écrites en C. Espérons que cette Q&R lance le bal pour de meilleures réponses:

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: $1est le gmail-meta3paramètre et fest la liste des fichiers correspondants. Combiné, cela signifie pour gmail-meta3, gmail-meta3-LAB-9999, etc., procédez comme suit
  • [[ ! "$f" == *"$2" ]] &&: $fest le même que fci-dessus. $2est le .bakparamètre passé. Combiné, cela signifie que si le nom de fichier ne se termine pas .bak(parce que nous ne voulons pas copier .baket créer .bak.bak), procédez comme suit
  • cp -a "$f" "$f$2"; copiez gmail-meta3 dans gmail-meta3.bak, etc.
  • done: boucle et récupère le nom de fichier suivant dans la gmail-meta3liste *.

cps gmail-meta3 .bak Exemple de sortie

En utilisant la question comme exemple, voici à quoi elle ressemble en action:

───────────────────────────────────────────────────────────────────────────────────────────
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$ 

Remarque: Cela utilise l' -aindicateur avec la cpcommande pour conserver les horodatages et vous donner une meilleure compréhension de vos sauvegardes de fichiers.

Notez que les copies de fichiers ont exactement la même date et l'heure que les originaux. Si le -aparamètre était omis, ils recevraient la date et l'heure actuelles et ne ressembleraient pas à une véritable sauvegarde, sauf que la taille du fichier serait la même.


6
les gens ne recommandent-ils pas toujours contre l'analysels
qwr

3
Puisque vous mentionnez, findje suppose que vous êtes conscient des dangers de l'analyse syntaxique ls? Mais dans votre cas, aucun n'est nécessaire: faites-le for file in "$1"*; do copy -a "$file" "$file$2"; done- c'est complètement sûr et beaucoup plus simple que tout type d'indirection via lsou findet une whileboucle.
Konrad Rudolph

@KonradRudolph Merci pour votre suggestion. J'ai implémenté et testé votre suggestion avec quelques modifications mineures.
WinEunuuchs2Unix

2

Une autre méthode pour répondre à vos besoins consiste à copier les fichiers dans un répertoire temporaire et à utiliser la renamecommande pour les renommer.

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

Si vous en avez besoin en tant que script, vous pouvez l'utiliser comme ça

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" .
}

Et vous pouvez l'utiliser comme ceci:

cps file bak

Ceci est un exemple

$ 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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.