Copiar grupo de archivos (Nombre de archivo *) a la copia de seguridad (Nombre de archivo * .bak)

13

Antecedentes

En Linux puedes:

  • Listar un grupo de archivos con ls Filename*
  • Eliminar un grupo de archivos con rm Filename*
  • Mover un grupo de archivos con mv Filename* /New/Directory
  • Pero no puede copiar un grupo de archivos con:cp Filename* *.bak

Cambiar el cpcomando de Linux para copiar el grupo de archivos

Tengo un grupo de archivos que me gustaría copiar sin ingresar los nombres uno por uno y usando el cpcomando:

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

¿Cómo puedo usar algo como el viejo comando de DOS copy gmail-meta3* *.bak?

No quiero escribir un comando similar cuatro veces:

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

Estoy buscando un script / función / aplicación que acepte parámetros para el grupo de nombre de archivo antiguo y nuevo y no algo con nombres de archivo codificados. Por ejemplo, un usuario puede escribir:

copy gmail-meta3* *.bak

o pueden escribir:

copy gmail-meta3* save-*
WinEunuuchs2Unix
fuente
1
El problema me parece que usa el operador glob dos veces que ninguno de sus otros comandos usa. bash no es lo suficientemente inteligente como para manejarlo.
qwr
2
@qwr, el hecho de que bash expanda los metacaracteres y tokenice la entrada antes de entregarla a un comando para que se ejecute es parte del diseño de los shells de UNIX. Intentar de alguna manera programar una excepción para el comando cp rompería toda la consistencia de bash, lo que no sería inteligente en absoluto. Como ejercicio, intente averiguar qué está sucediendo aquí y por qué es la expansión de metacaracteres del shell lo que lo hace así:touch aa ab ba; mkdir bb; cp a* b*; ls *
Mike S
@ Mike Gracias por los consejos. Ayer alguien más dijo que puede usar un comodín *para los nombres de los archivos de origen, pero no para los nombres de los archivos de destino. Como tal, el comodín sustituto (creo que ##fue sugerido pero me estoy inclinando hacia %) tendrá que usarse para el objetivo. Creo que esto es lo que estás reforzando? No esperaba cambiar el cpcomando en absoluto. Simplemente cree una secuencia de comandos de envoltura llamada copyque emuló (dentro de lo razonable) el comando de copia de DOS.
WinEunuuchs2Unix
@ WinEunuuchs2Unix esa persona estaba en lo correcto. Los metacaracteres del shell son independientes de los comandos. Por lo tanto, todos los comodines intentarán hacer coincidir todos los archivos que coincidan con el patrón. Si está tratando de hacer un programa de propósito general "unir todo y copiarlos a lo que sea que sean, pero agregue este sufijo", entonces sí, colocar un metacaracter sin escape ya que el objetivo probablemente no hará lo que desea. Porque todos los metacaracteres en la línea de comando del shell se expanden. Si SABE con certeza que el metacarácter objetivo nunca formará una coincidencia, podría usarlo, porque el shell no puede expandirlo.
Mike S
... pero eso sería algo feo. Es mejor usar un personaje especial. % o guión bajo son buenos, generalmente no son metacaracteres (pero tenga cuidado de usar% en un archivo crontab; es especial allí).
Mike S

Respuestas:

14

Aquí hay un ejemplo de un uso atípico de sed, que es aplicable para esta tarea:

sed -i.bak '' file-prefix*

De esta manera, en realidad, sedno cambiará los archivos, porque no proporcionamos ningún comando '', pero debido a la opción -i[suffix], creará una copia de seguridad de cada archivo. Encontré este enfoque cuando estaba buscando ¿Hay alguna forma de crear una copia de seguridad de un archivo, sin escribir su nombre dos veces?

pa4080
fuente
FYI: $ time sed -i.bak '' gmail-meta3*=real 0m0.069s
WinEunuuchs2Unix
Si los archivos que ya existe a continuación: real 0m0.037s. Si los archivos borrados y correr por segunda vez cerca de cpla velocidad: real 0m0.051s.
WinEunuuchs2Unix
@xiota Un punto interesante. Resulta que sedes más rápido cuando ya existen archivos de destino, pero cpes más lento cuando existen archivos de destino. De hecho, enjuago los cachés y las memorias intermedias en lugar de synccuando hago grandes pruebas de tiempo, pero esta vez no lo hice. Como esto puede estar divagando en una telenovela completamente diferente fuera del tema, lamento haber compartido los resultados de mi prueba :( ¿Es demasiado tarde para fingir que esta conversación nunca sucedió? . Tampoco es un disco duro, es un SSD Samsung Pro 960 NVMe en 4 canales.
WinEunuuchs2Unix
1
Probablemente no importa qué dispositivo de almacenamiento tenga para estas pruebas, si esto está sucediendo en una máquina Linux. El núcleo es extremadamente bueno para almacenar archivos en la memoria intermedia. Ese es el valor "buff / cache" cuando se usa el freecomando. Las escrituras reales en el dispositivo tienen lugar en un momento elegido por un algoritmo que tiene en cuenta la antigüedad de la memoria caché y la presión de la memoria en la máquina. Si está probando varias pruebas, las primeras lecturas de archivo saldrán del disco, pero las lecturas posteriores probablemente saldrán directamente de la memoria (vea sync; echo 3 > /proc/sys/vm/drop_caches).
Mike S
Ayer realicé algunas pruebas con archivos grandes de más de 2 GB y, sí, este enfoque es relativamente lento que el uso del cpcomando, pero no puedo decir que haya una diferencia de rendimiento significativa .
pa4080
13

Puedes usar find:

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

Eso encontrará en el directorio actual .todos los archivos con un nombre que coincida con el patrón global (tenga en cuenta las comillas simples alrededor del patrón para evitar el bloqueo de shell). Para cada archivo encontrado, se cpejecutará de nombre a nombre.bak. Los \; al final asegura que hará cada archivo individualmente en lugar de pasarlos todos a la vez. La profundidad máxima como 1 solo busca en el directorio actual en lugar de recurrir hacia abajo.

cbojar
fuente
1
¿Funcionará find . -max-depth 1 -name '"$1"'' -exec cp "{}" "{}$2" \;cuando $ 1 sea fuente y $ 2 sea extensión?
WinEunuuchs2Unix
$ 2 debería estar bien para sustituir siempre que sea razonable. $ 1 podría ser más complicado ya que no podemos hacer sustitución de variables dentro de comillas simples. No estoy seguro de antemano, pero podría ser posible usar $ 1 entre comillas dobles ya que el patrón se almacena en una cadena.
cbojar
11

Puedes usar un forbucle conbash . Normalmente, simplemente lo escribiría como una sola línea porque esta no es una tarea que realizo a menudo:

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

Sin embargo, si lo necesita como un 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
}

El uso es similar a rename. Probar:

pushd /tmp
mkdir tmp2
touch tmp2/test{001..100}     # create test files
ls tmp2
cps 's@^@prefix-@ ; s@[email protected]@' tmp2/test*    # create backups
cps 's@[email protected]@' tmp2/test*    # more backups ... will display errors
ls tmp2
\rm -r tmp2                   # cleanup
popd
xiota
fuente
FYI: $ time for f in gmail-meta3* ; do cp -a "$f" "${f}.bak" ; done=real 0m0.046s
WinEunuuchs2Unix
Um no, no quiero optimizar el tiempo. Son 0.046segundos, lo que significa 0 segundos para la percepción humana. Solo estaba tratando de mostrar cómo estaba probando las respuestas publicadas y transmitiendo cositas interesantes a los espectadores que vieron el sedcomando anterior. O al menos estaba interesado en compararme sedcon cp...
WinEunuuchs2Unix
Sin embargo, su solución cpes más rápida que la solución con sed. Así que es motivo de celebración :)
WinEunuuchs2Unix
(1)  -aes un operador de prueba no estándar. ¿Por qué no usar -e? (2) "No se puede crear el directorio temporal". es un mensaje de error algo engañoso. (3) ¿Por qué no solo usar mktemp -d? (4) Debe probar los estados de salida. Por ejemplo, deberías decir ! mkdir "$FOLDER" && echo "Unable to create temporary directory." && return 1 o  mkdir "$FOLDER" || { echo "Unable to create temporary directory."; return 1;}. Del mismo modo para cpy  rename(y tal vez incluso pushd, si quieres tener cuidado). … (Continúa)
G-Man dice 'Reincorporar a Monica' el
(Continúa) ... (5) Arrggghhhh! No diga $@; por ejemplo "$@". (5b)  No es necesario usar {y } cuando hace referencia a las variables de la forma en que lo está haciendo ( "${FOLDER}",  "${PATTERN}" y  "${file}"); acaba de hacer "$FOLDER",  "$PATTERN" y  "$file". (6) Esto supone que los archivos están en el directorio actual.  cps 's/$/.bak/' d/foocopiará d/fooa foo.bak en el directorio actual, no d/foo.bak.
G-Man dice 'Restablece a Monica' el
6

Lo más cercano que probablemente llegará al paradigma de DOS es mcp(desde el mmvpaquete):

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

Si zshestá disponible, su zmvmódulo contribuido está quizás un poco más cerca:

autoload -U zmv

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

Sin lsembargo, evitaría : una variante en su propia respuesta que sea segura para los espacios en blanco (incluidas las nuevas líneas) sería

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

o quizás

printf '%s\0' gmail-meta3* | xargs -0 -I{} cp -a -- {} {}.bak
conductor de acero
fuente
Entiendo que mmves el paquete, pero en los comentarios dices que el comando es mcppero luego en el comando que usas, mmvque también es un comando en el mmvpaquete. Me gusta la dirección de los printfejemplos y en un guión pulido me aseguraría de que se pasaran $ 1 y $ 2. +1 por hacer rodar la pelota :)
WinEunuuchs2Unix
@ WinEunuuchs2Unix disculpas - el mcp / mmv fue un brainfart. En realidad mcpes solo un sinónimo demmv -c
steeldriver
Pfft no te preocupes. Si tuviera un dólar por cada error tipográfico que hiciera, sería millonario :) Me gustaría aclarar el printfcomando que nunca he usado realmente. ¿Estás diciendo printf '%s\0' "$1"*que funcionaría si gmail-meta3se pasara como parámetro 1?
WinEunuuchs2Unix
@ WinEunuuchs2Unix Probablemente dejaría que el contexto de la llamada haga lo que quiera, es decir, cps gmail-meta3*y luego escriba printf '%s\0"$ @" | mientras ... `en la función. O simplemente use for f; do cp -- "$f" "$f.bak"; done(como la respuesta de xiota , pero como una función)
steeldriver
1
Tenga en cuenta que con zmvusted puede usar el modo "reemplazo de comodines", que me resulta un poco más fácil de asimilar:zmv -W -C 'gmail-meta3*' '*.bak'
0x5453
5

solución única rsync

Si solo desea hacer una copia de seguridad de sus archivos, puede copiarlos en un nuevo directorio

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

Esto copiará los Filenamearchivos de /path/to/dir/a /path/to/backupdirectory.


rsync + filerename

Si desea que sus archivos de respaldo tengan un sufijo, las cosas se ponen difíciles con rsync...

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

Esto sobrescribiría los archivos existentes ... con los archivos existentes ( -I) pero solo si son ( -u) más nuevos (que no lo son) y crean una copia de seguridad, con un sufijo.

También puede hacerlo en el mismo directorio. Pero mejor excluir las copias de seguridad existentes.

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

Robert Riedl
fuente
Me encanta, rsycncasí que voté, pero un método más simple sería cp Filename* /path/to/backup/dirporque los archivos no necesitarían *.bakuniquifier si estuvieran en un directorio separado.
WinEunuuchs2Unix
4

Este debe hacer lo solicitado:

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

Elegí ?porque #debe citarse cuando es el primer carácter del patrón; de lo contrario, se reconoce como el comienzo de un comentario.

Prueba:

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


Algunas versiones anteriores del script para agregar un sufijo:

Similar a la respuesta de @ WinEunuuchs2Unix, pero creo que es más flexible y no analizals :

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

Pon esto en tu .bashrc.

Uso:

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

Alternativa, con el sufijo como último argumento ( vía y vía ):

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

Uso:

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

pLumo
fuente
Buena codificación pero es un poco difícil después de décadas de usar Source y luego Target para cambiar el comando de copia a Target y luego a Source
WinEunuuchs2Unix
Se agregó la función con el sufijo en la parte posterior.
pLumo
Gracias eso es más intuitivo. Llamarlo sufijo es exacto en la forma en que mi respuesta lo codificó, pero en realidad es un objetivo o un destino. Otros usuarios podrían querer usar: copy gmail-meta3* old-meta3*. En mi respuesta no pude encontrar la forma de ingresar *al nombre del destino como mi pregunta solicitada ...
WinEunuuchs2Unix
El problema es que *es interpretado por el shell, por lo que la función no lo sabrá. Necesitaría algún otro carácter o citarlo, luego reemplácelo con el nombre de archivo original dentro de la función.
pLumo
Supongo que #podría usarse como comodín de reemplazo *. Entonces podrías escribir copy filenames# save-#. Creo que querrías que el carácter comodín sea el mismo para el origen y el destino.
WinEunuuchs2Unix
4

Escribí esta frase en mi ~/.bashrc. findSupongo que se pueden publicar respuestas mucho mejores usando . Incluso se podrían escribir mejores respuestas en C. Esperemos que este Q&A ponga en marcha la pelota para obtener mejores respuestas:

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: $1es el gmail-meta3parámetro y fes la lista de archivos que coinciden. Combinado esto significa para gmail-meta3, gmail-meta3-LAB-9999, etc., haga lo siguiente
  • [[ ! "$f" == *"$2" ]] &&: $fes lo mismo que farriba. $2es el .bakparámetro pasado Combinado esto significa que si el nombre de archivo no termina en .bak(porque no queremos copiar .baky crear .bak.bak), haga lo siguiente
  • cp -a "$f" "$f$2"; copie gmail-meta3 en gmail-meta3.bak, etc.
  • done: retrocede y toma el siguiente nombre de archivo en la gmail-meta3lista *.

cps gmail-meta3 .bak Salida de muestra

Usando la pregunta como ejemplo aquí es cómo se ve en acción:

───────────────────────────────────────────────────────────────────────────────────────────
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: Esto usa la -abandera con el cpcomando para preservar las marcas de tiempo y darle una mejor comprensión de sus copias de seguridad de archivos.

Observe cómo las copias de archivo tienen exactamente la misma fecha y hora que los originales. Si -ase omitiera el parámetro, se les daría la fecha y hora actuales y no se vería como una verdadera copia de seguridad, excepto que el tamaño del archivo sería el mismo.

WinEunuuchs2Unix
fuente
66
¿la gente no siempre recomienda no analizar?ls
qwr
3
Como mencionas find, supongo que eres consciente de los peligros del análisis ls. Pero en su caso tampoco es necesario: simplemente for file in "$1"*; do copy -a "$file" "$file$2"; done, esto es completamente seguro y mucho más simple que cualquier tipo de indirección a través de lso findy un whilebucle.
Konrad Rudolph
@KonradRudolph Gracias por su sugerencia. Implementé y probé su sugerencia con un par de cambios menores.
WinEunuuchs2Unix
2

Otro método para cumplir su requisito es copiar los archivos en un directorio temporal y usar el renamecomando para cambiarles el nombre.

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

Si lo necesita como script, puede usarlo así

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

Y puedes usarlo así:

cps file bak

Esto es un ejemplo

$ 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
Dan
fuente