Скопировать группу файлов (Имя файла *) в резервную копию (Имя файла * .bak)

13

Фон

В Linux вы можете:

  • Перечислите группу файлов с ls Filename*
  • Удалить группу файлов с rm Filename*
  • Переместить группу файлов с помощью mv Filename* /New/Directory
  • Но вы не можете скопировать группу файлов с:cp Filename* *.bak

Изменить cpкоманду Linux для копирования группы файлов

У меня есть группа файлов, которые я хотел бы скопировать, не вводя имена по одному и используя cpкоманду:

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

Как я могу использовать что-то вроде старой команды DOS copy gmail-meta3* *.bak?

Я не хочу набирать подобную команду четыре раза:

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

Я ищу скрипт / функцию / приложение, которое принимает параметры для старой и новой группы имен файлов, а не что-то с жестко закодированными именами файлов. Например, пользователь может ввести:

copy gmail-meta3* *.bak

или они могут напечатать:

copy gmail-meta3* save-*
WinEunuuchs2Unix
источник
1
Вопрос выглядит для меня дважды с помощью оператора glob, который не использует ни одна из ваших других команд. Bash не достаточно умен, чтобы справиться с этим.
Qwr
2
@qwr, тот факт, что bash расширяет метасимволы и токенизирует ввод перед передачей его исполняемой команде, является частью конструкции оболочек UNIX. Попытка каким-либо образом запрограммировать исключение для команды cp нарушит всю целостность bash, что вовсе не будет разумно. В качестве упражнения попытайтесь выяснить, что здесь происходит, и почему именно расширение метасимвола оболочки делает это так:touch aa ab ba; mkdir bb; cp a* b*; ls *
Mike S
@MikeS Спасибо за указатели. Вчера кто-то еще сказал, что вы можете использовать подстановочный знак *для исходных имен файлов, но не для целевых имен файлов. В качестве альтернативы подстановочный знак (я думаю, что это ##было предложено, но я склоняюсь %) должен будет использоваться для цели. Я думаю, это то, что вы усиливаете? Я не ожидал изменить cpкоманду вообще. Просто создайте скрипт-обертку, copyкоторая будет эмулировать (в пределах разумного) команду копирования DOS.
WinEunuuchs2Unix
@ WinEunuuchs2Unix этот человек был прав. Метасимволы оболочки не зависят от команд. Таким образом, все подстановочные знаки будут пытаться сопоставить все файлы, которые соответствуют шаблону. Если вы пытаетесь заставить универсальную программу «сопоставить все и скопировать их в то, чем они были, но добавить этот суффикс», то тогда да, если в качестве цели ставится неэкранированный метасимвол, то, вероятно, не будет выполнять то, что вы хотите. Потому что все метасимволы в командной строке оболочки раскрыты. Если вы точно знаете, что целевой метасимвол никогда не сформирует совпадение, вы можете использовать его, потому что оболочка не может его развернуть.
Майк С.
... но это было бы ужасно. Лучше использовать специальный символ. % или подчеркивание - это хорошо, они обычно не являются метасимволами (но будьте осторожны с использованием% в файле crontab; там это особенное).
Майк С.

Ответы:

14

Вот пример одного нетипичного использования sed, применимого для этой задачи:

sed -i.bak '' file-prefix*

Таким образом, на самом деле, sedфайлы не будут изменены, потому что мы не предоставили никаких команд '', но из-за этой опции -i[suffix]она создаст резервную копию каждого файла. Я нашел этот подход, когда искал. Есть ли способ создать резервную копию файла, не вводя его имя дважды?

pa4080
источник
К вашему сведению: $ time sed -i.bak '' gmail-meta3*=real 0m0.069s
WinEunuuchs2Unix
Если файлы уже существует , то: real 0m0.037s. Если файлы удалены и запустить второй раз близко к cpскорости: real 0m0.051s.
WinEunuuchs2Unix
@ xiota Интересный момент. Оказывается, sedбыстрее, когда целевые файлы уже существуют, но cpмедленнее, когда целевые файлы существуют. Я на самом деле сбрасываю кеш и буферы, а не syncкогда делаю большие временные тесты, но на этот раз я этого не делал. Поскольку это может отвлечься от совершенно другой тематической мыльной оперы, я сожалею, что поделился результатами своего теста :( Не слишком ли поздно сделать вид, что этого разговора никогда не было? Размер метаданных сообщения Gmail FYI составляет 2,5 МБ, а 3 файла индекса - около 800 КБ Также это не жесткий диск, это SSD Samsung Pro 960 NVMe на 4 каналах
WinEunuuchs2Unix
1
Вероятно, не имеет значения, какое устройство хранения у вас для этих тестов, если это происходит на компьютере с Linux. Ядро очень хорошо буферизует файлы в памяти. Вот что означает «buff / cache» при использовании freeкоманды. Фактическая запись на устройство происходит в момент, выбранный алгоритмом, который учитывает возраст кеша и нагрузку на память машины. Если вы пытаетесь выполнить несколько тестов, то первое чтение файла будет происходить с диска, но последующее чтение, скорее всего, будет происходить прямо из памяти (см. sync; echo 3 > /proc/sys/vm/drop_caches).
Майк С
Вчера я провел несколько тестов с большими файлами размером более 2 ГБ и - да, этот подход относительно медленный, чем использование cpкоманды, но я не могу сказать, что есть существенная разница в производительности .
pa4080
13

Вы можете использовать find:

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

При этом в текущем каталоге будут найдены .все файлы с именем, совпадающим с шаблоном glob (обратите внимание на одинарные кавычки вокруг шаблона, чтобы предотвратить слипание оболочки). Для каждого найденного файла он будет cpисполняться от имени к имени. \; в конце гарантирует, что он будет делать каждый файл отдельно, а не передавать их все сразу. Максимальная глубина, равная 1, ищет только каталог cuurent, а не рекурсию вниз.

cbojar
источник
1
Будет ли это работать, find . -max-depth 1 -name '"$1"'' -exec cp "{}" "{}$2" \;когда $ 1 является источником, а $ 2 является расширением?
WinEunuuchs2Unix
$ 2 должно быть в порядке, чтобы заменить, если это разумно. $ 1 может быть сложнее, так как мы не можем сделать подстановку переменных внутри одинарных кавычек. Я не уверен в этом, но возможно использовать $ 1 в двойных кавычках, поскольку шаблон хранится в строке.
cbojar
11

Вы можете использовать forцикл сbash . Обычно я бы просто набрал его как однострочное, потому что это не та задача, которую я выполняю часто:

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

Однако, если вам это нужно как скрипт:

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
}

Использование аналогично rename. Тестировать:

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
xiota
источник
К вашему сведению: $ time for f in gmail-meta3* ; do cp -a "$f" "${f}.bak" ; done=real 0m0.046s
WinEunuuchs2Unix
Нет, я не хочу оптимизировать время. Это 0.046секунды, что означает 0 секунд для человеческого восприятия. Я просто пытался показать, как я тестировал опубликованные ответы и передавал интересные кусочки зрителям, которые смотрели на sedкоманду выше. Или, по крайней мере, я был заинтересован в сравнении sedс cp....
WinEunuuchs2Unix
Ваше решение с cpбыстрее, чем решение с sedхотя. Так что это повод для праздника :)
WinEunuuchs2Unix
(1)  -aявляется нестандартным тестовым оператором. Почему бы не использовать -e? (2) «Невозможно создать временный каталог». является несколько вводящим в заблуждение сообщением об ошибке. (3) Почему бы просто не использовать mktemp -d? (4) Вы должны проверить статусы выхода. Например, вы должны сказать ! mkdir "$FOLDER" && echo "Unable to create temporary directory." && return 1 или  mkdir "$FOLDER" || { echo "Unable to create temporary directory."; return 1;}. Аналогично для cpи  rename(и, может быть, даже pushd, если вы хотите быть осторожным). … (Продолжение)
G-Man говорит «Восстановить Монику»
(Продолжение)… (5) Arrggghhhh! Не говори $@; скажем "$@". (5b)  Нет необходимости использовать {и } когда вы ссылаетесь на переменные так, как вы делаете ( "${FOLDER}",  "${PATTERN}" и  "${file}"); просто делай "$FOLDER",  "$PATTERN" и  "$file". (6) Предполагается, что файлы находятся в текущем каталоге.  cps 's/$/.bak/' d/fooскопирует d/fooк foo.bak в текущем каталоге, а не d/foo.bak.
G-Man говорит: «Восстановите Монику»
6

Самое близкое, что вы, вероятно, получите к парадигме DOS mcp(из mmvпакета):

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

Если zshдоступно, его zmvдобавленный модуль, возможно, немного ближе:

autoload -U zmv

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

Я бы избегал ls- вариант вашего собственного ответа, который безопасен для пробелов (включая переводы строк), будет

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

или возможно

printf '%s\0' gmail-meta3* | xargs -0 -I{} cp -a -- {} {}.bak
steeldriver
источник
Я понимаю, mmvчто это пакет, но в комментариях вы говорите, что команда есть, mcpно затем в используемой вами команде, mmvкоторая также является командой в mmvпакете. Мне нравится направление printfпримеров, и в отточенном сценарии я бы гарантировал, что 1 и 2 доллара были переданы. +1 для того, чтобы получить катящийся шар :)
WinEunuuchs2Unix
@ WinEunuuchs2Unix извиняюсь - mcp / mmv был мозговым штурмом. На самом деле mcpэто просто синоним дляmmv -c
Steeldriver
Не беспокойся. Если бы у меня был доллар за каждую сделанную мной опечатку, я бы стал миллионером :) Мне бы хотелось получить разъяснения по поводу printfкоманды, которой я никогда не пользовался. Вы говорите, printf '%s\0' "$1"*будет работать, если gmail-meta3был передан в качестве параметра 1?
WinEunuuchs2Unix
@ WinEunuuchs2Unix Я бы, вероятно, позволил вызывающему контексту выполнить глобализацию, то есть cps gmail-meta3*написать printf '%s\0«$ @» | пока ... `в функции. Или просто используйте for f; do cp -- "$f" "$f.bak"; done(как ответ Xiota , но как функцию)
Steeldriver
1
Обратите внимание, что с zmvвами можно использовать режим «подстановки подстановочных знаков», который, как мне кажется, немного проще:zmv -W -C 'gmail-meta3*' '*.bak'
0x5453
5

rsync только решение

Если вы просто хотите сделать резервную копию своих файлов, вы можете скопировать их в новый каталог

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

Это скопирует Filenameфайлы из /path/to/dir/в /path/to/backupdirectory.


rsync + имя файла

Если вы хотите, чтобы у ваших файлов резервных копий был суффикс, все становится на свои места rsync...

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

Это перезапишет существующие файлы ... существующими файлами ( -I), но только если они ( -u) новее (а они не являются) и создадут резервную копию с суффиксом.

Вы также можете сделать это в том же каталоге. Но лучше исключить существующие резервные копии.

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

Роберт Ридл
источник
Я люблю, rsycncтак что я проголосовал, но, более простой метод был бы, cp Filename* /path/to/backup/dirпотому что файлы не нуждались *.bakбы в uniquifier, если бы они были в отдельном каталоге.
WinEunuuchs2Unix
4

Это следует сделать в соответствии с просьбой:

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

Использование:

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

Я выбрал, ?потому что #должен быть заключен в кавычки, когда это первый символ шаблона, иначе он распознается как начало комментария.

Тестовое задание:

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


Некоторые более ранние версии скрипта для добавления суффикса:

Похоже на ответ @ WinEunuuchs2Unix, но я думаю, что более гибкий, а не синтаксический анализls :

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

Поместите это в свой .bashrc.

Использование:

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

Альтернатива, с суффиксом в качестве последнего аргумента ( через и через ):

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

Использование:

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

pLumo
источник
Хорошее кодирование, но это довольно сложно после десятилетий использования Source, затем Target, чтобы переключить команду копирования на Target, а затем Source
WinEunuuchs2Unix,
Добавлена ​​функция с суффиксом сзади.
pLumo
Спасибо, что более интуитивно понятно. Называть его суффиксом точно, как мой ответ закодировал его, но это действительно цель или пункт назначения. Другие пользователи могут хотеть использовать: copy gmail-meta3* old-meta3*. В своем ответе я не мог понять, как попасть *в имя пункта назначения, как мой вопрос ...
WinEunuuchs2Unix
Проблема в том, что *интерпретируется оболочкой, поэтому функция не будет об этом знать. Вам понадобится какой-нибудь другой символ или кавычка, а затем замените его исходным именем файла внутри функции.
pLumo
Я думаю, что #может быть использован в качестве замены подстановочного знака для *? Чтобы вы могли печатать copy filenames# save-#. Я думаю, вы хотите, чтобы подстановочный знак был одинаковым для источника и цели.
WinEunuuchs2Unix
4

Я написал эту строку в мой ~/.bashrc. findПолагаю, гораздо лучшие ответы с помощью можно выложить. Еще лучше ответы могут быть написаны на языке C. Надеюсь, этот Q & A заставляет шарик катиться за лучшие ответы:

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это gmail-meta3параметр и fсписок совпадающих файлов. В сочетании это означает, что gmail-meta3, gmail-meta3-LAB-9999 и т. Д. Делают следующее
  • [[ ! "$f" == *"$2" ]] &&: $fтакой же, как fуказано выше. $2это .bakпереданный параметр. В сочетании это означает, что если имя файла не заканчивается .bak(потому что мы не хотим копировать .bakи создавать .bak.bak), то сделайте следующее
  • cp -a "$f" "$f$2"; скопируйте gmail-meta3 в gmail-meta3.bak и т. д.
  • done: вернуться назад и захватить следующее имя файла в gmail-meta3* списке.

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

Примечание. При этом используется -aфлаг с cpкомандой для сохранения временных отметок и лучшего понимания ваших резервных копий файлов.

Обратите внимание, что копии файлов имеют ту же дату и время, что и оригиналы. Если -aпараметр не указан, им будут даны текущая дата и время, и он не будет выглядеть как настоящая резервная копия, за исключением того, что размер файла будет таким же.

WinEunuuchs2Unix
источник
6
не всегда люди рекомендуют против разбораls
qwr
3
Поскольку вы упомянули, findя предполагаю, что вы знаете об опасностях разбора ls? Но в вашем случае в этом нет необходимости: просто сделайте for file in "$1"*; do copy -a "$file" "$file$2"; doneэто - это абсолютно безопасно и намного проще, чем любое косвенное обращение через lsили findи whileцикл.
Конрад Рудольф
@KonradRudolph Спасибо за ваше предложение. Я реализовал и проверил ваше предложение с парой небольших изменений.
WinEunuuchs2Unix
2

Еще один способ выполнить ваше требование - скопировать файлы во временный каталог и использовать renameкоманду для их переименования.

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

Если вам нужен сценарий, вы можете использовать его так

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

И вы можете использовать это так:

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: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
Дэн
источник