Copiar grupo de arquivos (Nome do arquivo *) para backup (Nome do arquivo * .bak)

13

fundo

No Linux, você pode:

  • Listar um grupo de arquivos com ls Filename*
  • Remova um grupo de arquivos com rm Filename*
  • Mova um grupo de arquivos com mv Filename* /New/Directory
  • Mas você não pode copiar um grupo de arquivos com:cp Filename* *.bak

Alterar cpcomando do Linux para copiar o grupo de arquivos

Eu tenho um grupo de arquivos que gostaria de copiar sem digitar os nomes um por um e usar o cpcomando:

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

Como posso usar algo como o antigo comando do DOS copy gmail-meta3* *.bak?

Não quero digitar comando semelhante quatro vezes:

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

Estou procurando um script / função / aplicativo que aceite parâmetros para o grupo de nome de arquivo antigo e novo e não algo com nomes de arquivos codificados. Por exemplo, um usuário pode digitar:

copy gmail-meta3* *.bak

ou eles podem digitar:

copy gmail-meta3* save-*
WinEunuuchs2Unix
fonte
1
O problema me parece usar o operador glob duas vezes que nenhum dos seus outros comandos usa. bash não é inteligente o suficiente para lidar com isso.
QWR
2
@qwr, o fato de o bash expandir metacaracteres e tokenizar a entrada antes de entregá-la a um comando a ser executado faz parte do design dos shells do UNIX. De alguma forma, tentar programar uma exceção para o comando cp quebraria toda a consistência do bash, o que não seria nada inteligente. Como exercício, tente descobrir o que está acontecendo aqui e por que é a expansão de metacaracteres do shell que o torna assim:touch aa ab ba; mkdir bb; cp a* b*; ls *
Mike S
@ MikeS Obrigado pelos ponteiros. Ontem, alguém disse que você pode usar um curinga *para os nomes de arquivos de origem, mas não para os nomes de arquivos de destino. Como um curinga substituto (acho que ##foi sugerido, mas estou inclinado a %), terá que ser usado para o destino. Eu acho que é isso que você está reforçando? Eu não esperava mudar o cpcomando. Basta criar um script de invólucro chamado copyque emulou (dentro do motivo) o comando de cópia do DOS.
WinEunuuchs2Unix 27/06/19
@ WinEunuuchs2Unix essa pessoa estava correta. Os metacaracteres do shell são independentes dos comandos. Portanto, todos os curingas tentarão corresponder a todos os arquivos que correspondem ao padrão. Se você está tentando fazer com que um programa de uso geral "combine tudo e copie-o para o que quer que fosse, mas adicione esse sufixo", sim, coloque um metacaractere sem escape, pois o alvo provavelmente não fará o que você deseja. Porque todos os metacaracteres na linha de comando do shell são expandidos. Se você sabe com certeza que o metacaractere alvo nunca formará uma correspondência, você pode usá-lo - porque o shell não pode expandi-lo.
Mike S
... mas isso seria uma coisa feia de se fazer. Melhor usar um caractere especial. % ou sublinhado são bons, geralmente não são metacaracteres (mas tenha cuidado ao usar% em um arquivo crontab; é especial lá).
Mike S

Respostas:

14

Aqui está um exemplo de um uso atípico de sed, aplicável a esta tarefa:

sed -i.bak '' file-prefix*

Dessa forma, na verdade, sednão alterará os arquivos, porque não fornecemos nenhum comando '', mas devido à opção, -i[suffix]ele criará uma cópia de backup de cada arquivo. Encontrei essa abordagem quando estava pesquisando. Existe alguma maneira de criar uma cópia de backup de um arquivo, sem digitar seu nome duas vezes?

pa4080
fonte
FYI: $ time sed -i.bak '' gmail-meta3*=real 0m0.069s
WinEunuuchs2Unix
Se os arquivos já existe então: real 0m0.037s. Se os arquivos apagados e executar uma segunda vez perto de cpvelocidade: real 0m0.051s.
WinEunuuchs2Unix 26/06/19
@xiota Um ponto interessante. Acontece que sedé mais rápido quando os arquivos de destino já existem, mas cpé mais lento quando os arquivos de destino existem. Na verdade, eu libero caches e buffers, em vez de syncfazer grandes testes de temporização, mas não o fiz desta vez. Como isso pode se transformar em uma novela totalmente fora de questão, lamento compartilhar os resultados de meus testes :( É tarde demais para fingir que essa conversa nunca aconteceu? . Também não é um disco rígido, é um Samsung Pro 960 NVMe SSD em 4 canais.
WinEunuuchs2Unix
1
Provavelmente não importa qual dispositivo de armazenamento você possui para esses testes, se isso estiver acontecendo em uma máquina Linux. O kernel é extremamente bom em armazenar arquivos em buffer na memória. É esse o valor do "buff / cache" ao usar o freecomando As gravações reais no dispositivo ocorrem no momento escolhido por um algoritmo que leva em consideração a idade do cache e a pressão da memória na máquina. Se você estiver tentando vários testes, as primeiras leituras do arquivo sairão do disco, mas as leituras subsequentes provavelmente ficarão sem memória (veja sync; echo 3 > /proc/sys/vm/drop_caches).
Mike S
Ontem fiz alguns testes com arquivos grandes com mais de 2 GB e - sim, essa abordagem é relativamente lenta do que usar o cpcomando, mas não posso dizer que haja alguma diferença significativa de desempenho .
pa4080
13

Você pode usar find:

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

Isso encontrará no diretório atual .todos os arquivos com um nome correspondente ao padrão glob (lembre-se das aspas simples ao redor do padrão para evitar que o shell fique desfocado). Para cada arquivo encontrado, ele será cpexecutado de name para name.bak. O \; no final, garante que cada arquivo seja executado individualmente, em vez de passar todos eles de uma vez. A profundidade máxima como 1 somente pesquisa o diretório atual em vez de recursar para baixo.

cbojar
fonte
1
Funcionará find . -max-depth 1 -name '"$1"'' -exec cp "{}" "{}$2" \;quando $ 1 for fonte e $ 2 for extensão?
WinEunuuchs2Unix
US $ 2 devem ser aprovados para substituir, desde que sejam razoáveis. $ 1 pode ser mais complicado, pois não podemos fazer a substituição de variáveis ​​entre aspas simples. Não tenho certeza de imediato, mas pode ser possível usar $ 1 entre aspas duplas, pois o padrão é armazenado em uma string.
cbojar
11

Você pode usar um forloop combash . Normalmente, eu apenas digitava como uma linha, porque essa não é uma tarefa que realizo com frequência:

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

No entanto, se você precisar dele como um 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
}

O uso é semelhante a rename. Testar:

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
fonte
FYI: $ time for f in gmail-meta3* ; do cp -a "$f" "${f}.bak" ; done=real 0m0.046s
WinEunuuchs2Unix
Hum, não, eu não quero otimizar por tempo. É 0.046segundos, o que significa 0 segundos para a percepção humana. Eu estava apenas tentando mostrar como estava testando as respostas postadas e transmitindo petiscos interessantes para os espectadores que olharam o sedcomando acima. Ou, pelo menos, eu estava interessado em comparar sedcom cp.... #
WinEunuuchs2Unix 26/06/19
Sua solução com cpé mais rápida que a solução com sedembora. Portanto, é um motivo de comemoração :)
WinEunuuchs2Unix
(1)  -aé um operador de teste não padrão. Por que não usar -e? (2) "Não foi possível criar o diretório temporário". é uma mensagem de erro um tanto enganadora. (3) Por que não usar apenas mktemp -d? (4) Você deve testar os status de saída. Por exemplo, você deve dizer ! mkdir "$FOLDER" && echo "Unable to create temporary directory." && return 1 ou  mkdir "$FOLDER" || { echo "Unable to create temporary directory."; return 1;}. Da mesma forma para cpe  rename(e talvez até pushd, se você quiser ter cuidado). ... (continua)
G-Man diz 'Restabelecer Monica
(Continua) ... (5) Arrggghhhh! Não diga $@; dizer "$@". (5b)  Não há necessidade de usar {e } quando você faz referência a variáveis ​​do jeito que está fazendo ( "${FOLDER}",  "${PATTERN}" e  "${file}"); apenas faça "$FOLDER",  "$PATTERN" e  "$file". (6) Isso pressupõe que os arquivos estejam no diretório atual.  cps 's/$/.bak/' d/foocopiará d/foopara foo.bak no diretório atual, não d/foo.bak.
G-Man diz 'Reinstate Monica'
6

O mais próximo que você provavelmente chegará do paradigma DOS é mcp(do mmvpacote):

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

Se zshestiver disponível, seu zmvmódulo contribuído talvez seja um pouco mais próximo:

autoload -U zmv

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

Eu evitaria lsindependentemente - uma variante da sua própria resposta que seja segura para espaços em branco (incluindo novas linhas) seria

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

ou talvez

printf '%s\0' gmail-meta3* | xargs -0 -I{} cp -a -- {} {}.bak
chave de aço
fonte
Eu entendo mmvé o pacote, mas nos comentários você diz que o comando é, mcpmas depois no comando que você usa, mmvque também é um comando no mmvpacote. Gosto da direção dos printfexemplos e, em um script polido, garantiria que $ 1 e $ 2 fossem passados. +1 para obter rolamento bola :)
WinEunuuchs2Unix
@ Desculpas do WinEunuuchs2Unix - o mcp / mmv foi um peido cerebral. Na verdade, mcpé apenas um sinônimo parammv -c
steeldriver
Pfft não se preocupe. Se eu tivesse um dólar por cada erro de digitação cometido, seria um milionário :) Gostaria de esclarecimentos sobre o printfcomando que nunca usei realmente. Você está dizendo printf '%s\0' "$1"*que funcionaria se gmail-meta3fosse passado como parâmetro 1?
WinEunuuchs2Unix
@ WinEunuuchs2Unix Eu provavelmente deixaria o contexto de chamada fazer o globbing, ou seja, cps gmail-meta3*e depois escrever printf '%s\0"$ @" | enquanto ... `na função. Ou simplesmente usar for f; do cp -- "$f" "$f.bak"; done(como resposta de xiota , mas como uma função)
steeldriver
1
Observe que zmvvocê pode usar o modo "substituição de curinga", que eu acho um pouco mais fácil de zmv -W -C 'gmail-meta3*' '*.bak'
entender
5

única solução rsync

Se você quiser apenas fazer backup de seus arquivos, poderá copiá-los para um novo diretório

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

Isso copiará os Filenamearquivos de /path/to/dir/para /path/to/backupdirectory.


rsync + nome do arquivo

Se você deseja que seus arquivos de backup tenham um sufixo, as coisas ficam rsync...

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

Isso sobrescreveria os arquivos existentes ... pelos arquivos existentes ( -I), mas apenas se eles fossem ( -u) mais recentes (que não são) e criando um backup, com um sufixo.

Você também pode fazer isso no mesmo diretório. Mas é melhor excluir os backups existentes.

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

Robert Riedl
fonte
Eu amo rsycncque eu votei, mas um método mais simples seria cp Filename* /path/to/backup/dirporque os arquivos não precisariam de um *.bakuniquificador se estivessem em um diretório separado.
WinEunuuchs2Unix 26/06/19
4

Este deve fazer conforme 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

Eu escolhi ?porque #deve ser citado quando é o primeiro caractere do padrão, caso contrário, é reconhecido como o início de um comentário.

Teste:

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


Algumas versões anteriores do script para adicionar um sufixo:

Semelhante à resposta do @ WinEunuuchs2Unix, mas acho mais flexível e não analisals :

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

Coloque isso no seu .bashrc.

Uso:

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

Alternativa, com o sufixo como último argumento ( 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

pLumo
fonte
Agradável de codificação, mas é meio difícil depois de décadas de uso Fonte então alvo de comando de cópia interruptor para Alvo seguida Fonte
WinEunuuchs2Unix
Adicionada a função com o sufixo na parte de trás.
PLumo
Obrigado, que é mais intuitivo. Chamá-lo de sufixo é preciso da maneira que minha resposta o codificou, mas é realmente um destino ou destino. Outros usuários pode querer usar: copy gmail-meta3* old-meta3*. Em minha resposta eu não conseguia descobrir como chegar *ao nome do destino como a minha pergunta solicitada ...
WinEunuuchs2Unix
O problema é que *é interpretado pelo shell, portanto a função não o saberá. Você precisaria de outro caractere ou citá-lo e substituí-lo pelo nome do arquivo original na função.
PLumo
Eu acho que o #poderia ser usado como um curinga substituto para *? Então você pode digitar copy filenames# save-#. Eu acho que você gostaria que o caractere curinga fosse o mesmo para origem e destino.
WinEunuuchs2Unix 26/06/19
4

Eu escrevi essa frase no meu ~/.bashrc. Respostas muito melhores findpodem ser postadas, suponho. Respostas ainda melhores podem ser escritas em C. Espero que essas perguntas e respostas façam a bola rolar para melhores respostas:

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é o gmail-meta3parâmetro e fé a lista de arquivos correspondentes. Combinado isso significa para o gmail-meta3, gmail-meta3-LAB-9999, etc., faça o seguinte
  • [[ ! "$f" == *"$2" ]] &&: $fé o mesmo que facima. $2é o .bakparâmetro passado. Combinado isso significa que, se o nome do arquivo não terminar .bak(porque não queremos copiar .bake criar .bak.bak), faça o seguinte
  • cp -a "$f" "$f$2"; copiar gmail-meta3 para gmail-meta3.bak, etc.
  • done: volte e pegue o próximo nome de arquivo na gmail-meta3lista *.

cps gmail-meta3 .bak Saída de amostra

Usando a pergunta como exemplo, veja como ela fica em ação:

───────────────────────────────────────────────────────────────────────────────────────────
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: Isso usa o -asinalizador com o cpcomando para preservar os carimbos de data e hora e oferecer uma melhor compreensão dos backups de arquivos.

Observe como as cópias dos arquivos têm exatamente a mesma data e hora dos originais. Se o -aparâmetro fosse omitido, eles receberiam a data e a hora atuais e não pareceriam um backup verdadeiro, exceto que o tamanho do arquivo seria o mesmo.

WinEunuuchs2Unix
fonte
6
as pessoas sempre não recomendam a análisels
qwr
3
Desde que você mencionou find, presumo que esteja ciente dos perigos da análise ls? Mas no seu caso, nenhum dos dois é necessário: apenas faça for file in "$1"*; do copy -a "$file" "$file$2"; done- isso é completamente seguro e muito mais simples do que qualquer tipo de indireto via lsou finde um whileloop.
Konrad Rudolph
@KonradRudolph Obrigado pela sua sugestão. Eu implementei e testei sua sugestão com algumas pequenas alterações.
WinEunuuchs2Unix
2

Outro método para atingir seus requisitos é copiar os arquivos para um diretório temporário e usar o renamecomando para renomeá-los.

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

Se você precisar dele como um script, poderá usá-lo assim

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 você pode usá-lo assim:

cps file bak

Isto é um exemplo

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