将文件组(Filename *)复制到备份(Filename * .bak)


13

背景

在Linux中,您可以:

  • 列出一组文件 ls Filename*
  • 使用以下命令删除一组文件 rm Filename*
  • 使用移动文件组 mv Filename* /New/Directory
  • 但是您不能使用以下命令复制一组文件:cp Filename* *.bak

更改Linux cp命令以复制文件组

我有一组文件要复制,而不用一个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-*

1
在我看来,这个问题使用了glob运算符两次,您的其他命令都没有使用过。bash不够聪明,无法处理它。
qwr

2
@qwr是bash扩展元字符并在将输入交给执行命令之前对输入进行标记化的事实,这是UNIX shell设计的一部分。以某种方式尝试为cp命令编程异常将破坏bash的整个一致性,这一点都不明智。作为练习,请尝试弄清楚这里发生了什么,以及为什么使它如此的是壳的元字符扩展:touch aa ab ba; mkdir bb; cp a* b*; ls *
Mike S

@MikeS感谢您的指导。昨天有人说您可以*对源文件名使用通配符,但对目标文件名不能使用通配符。因此,必须将替代通配符(我认为##是建议使用,但我倾向于%)用于目标。我认为这就是您要加强的?我没想到根本不会更改cp命令。只需创建一个称为wrapper脚本的脚本即可(copy出于某种原因)模拟DOS复制命令。
WinEunuuchs2Unix

@ WinEunuuchs2Unix那个人是正确的。外壳元字符与命令无关。因此,所有通配符都将尝试匹配与该模式匹配的所有文件。如果您要使通用的“匹配所有内容并将它们复制到原来的位置,但添加此后缀”程序,那么可以,将未转义的元字符作为目标可能无法满足您的要求。因为shell命令行中的所有元字符都已扩展。如果您确定目标元字符永远不会构成匹配项,则可以使用它-因为shell无法扩展它。
Mike S

...但这是一件丑陋的事情。最好使用特殊字符。%或下划线是很好的,它们通常不是元字符(但请注意在crontab文件中使用%;在此很特殊)。
Mike S

Answers:


14

这是的一种非典型用法的示例,sed适用于此任务:

sed -i.bak '' file-prefix*

实际上,这样sed就不会更改文件,因为我们没有提供任何命令'',但是由于该选项-i[suffix],它将为每个文件创建一个备份副本。我在搜索时发现了这种方法。 有没有办法创建文件的备份副本,而无需两次输入文件名?


仅供参考:$ time sed -i.bak '' gmail-meta3*=real 0m0.069s
WinEunuuchs2Unix

如果文件已经存在,则:real 0m0.037s。如果文件被删除并再次以接近cp速度的速度运行:real 0m0.051s
WinEunuuchs2Unix

@xiota一个有趣的观点。sed当目标文件已经存在时,结果更快,但是cp当目标文件存在时,结果慢。我实际上是刷新缓存和缓冲区,而不是sync在进行大型时序测试时刷新,但是这次我都没有执行。由于这可以脱离整个主题之外的肥皂剧,我很遗憾分享我的测试结果:(假装此对话从未为时过晚吗?FYI gmail消息元数据大小为2.5 MB,3个索引文件约为800 KB 。它也不是硬盘,而是4通道的Samsung Pro 960 NVMe SSD
WinEunuuchs2Unix

1
如果这是在Linux机器上发生的,那么对于这些​​测试,您所拥有的存储设备可能并不重要。内核非常擅长在内存中缓冲文件。这就是使用free命令时的“ buff / cache”值。实际写入设备的时间是由算法选择的,该算法考虑了缓存的寿命和计算机上的内存压力。如果您正在尝试多个测试,则第一个文件读取将从磁盘上删除,但随后的读取很有可能直接从内存中取出(请参阅参考资料sync; echo 3 > /proc/sys/vm/drop_caches)。
Mike S

昨天,我对2GB以上的大文件进行了很少的测试,是的,这种方法比使用该cp命令要慢,但是我不能说存在明显的性能差异
pa4080

13

您可以使用find

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

这将在当前目录中找到.所有名称与glob模式匹配的文件(请注意该模式周围的单引号以防止shell globbing)。对于找到的每个文件,它将cp从name到name.bak执行。\; 最后,确保它将单独处理每个文件,而不是一次传递所有文件。最大深度为1仅搜索当前目录,而不向下递归。


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

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

嗯,我不想优化时间。它是0.046秒,表示人类感知为0秒。我只是想展示如何测试发布的答案,并将有趣的花絮传递给查看sed上面命令的查看者。或者至少,我很感兴趣,比较sedcp....
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说'Resstate Monica''Jun

(续)...(5)Arrggghhhh!不要说$@; 说"$@"。(5B)  没有必要使用{,并 }在引用变量,你正在做的方式("${FOLDER}",  "${PATTERN}" 和  "${file}"); 只要做"$FOLDER",  "$PATTERN" 和  "$file"。(6)假设文件位于当前目录中。  cps 's/$/.bak/' d/foo将复制d/foofoo.bak 当前目录中,而不是d/foo.bak
G-Man说'Resstate Monica''Jun

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

我了解mmv它是软件包,但在注释中您说的是命令,mcp但是在您使用mmv的命令中它也是mmv软件包中的命令。我喜欢printf示例的方向,在优美的脚本中,我确保传递了$ 1和$ 2。+1使球滚动:)
WinEunuuchs2Unix

@ WinEunuuchs2Unix道歉-MCP / MMV是个明智的选择。其实mcp只是“ mmv -c
钢起子”

不用担心。如果我每做一次错字都赚一美元,我将成为百万富翁:)我想澄清一下printf我从未真正使用过的命令。您是说printf '%s\0' "$1"*如果gmail-meta3将其作为参数1传递会起作用吗?
WinEunuuchs2Unix

@ WinEunuuchs2Unix我可能会让调用上下文进行glob,即cps gmail-meta3*然后写printf '%s\0“ $ @” | 在功能中。。。或简单地使用for f; do cp -- "$f" "$f.bak"; done(如xiota的答案,但作为函数)
钢铁驾驶员

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 + filerename

如果您希望备份文件带有后缀,则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如果文件位于单独的目录中,则不需要唯一化符。
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*

另一种方法,将后缀作为最后一个参数(viavia):

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

用法:

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


编码不错,但是经过数十年的使用Source然后Target来将复制命令切换到Target然后Source
WinEunuuchs2Unix

添加了带有后缀的功能。
pLumo

谢谢,这更直观。以我的答案编码的方式来称呼后缀是准确的,但实际上它是目标或目的地。其他用户可能要使用:copy gmail-meta3* old-meta3*。在我的回答中,我无法*像我的问题一样想出如何进入目的地名称的方法
WinEunuuchs2Unix

问题是*由外壳解释,因此函数将不知道它。您可能需要其他一些字符或将其引号,然后将其替换为函数中的原始文件名。
pLumo

我猜#可以用作替代通配符*吗?所以你可以输入copy filenames# save-#。我想您希望源和目标的通配符相同。
WinEunuuchs2Unix

4

我把这单线写进了我的~/.bashrcfind我想可以发布使用更好的答案。甚至可以用C编写更好的答案。希望通过此问答可以取得更好的答案:

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$1gmail-meta3参数,f是匹配文件的列表。结合使用gmail-meta3,gmail-meta3-LAB-9999等的方法,请执行以下操作
  • [[ ! "$f" == *"$2" ]] &&$ff上述相同。$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$ 

注意:-acp命令一起使用标志来保留时间戳记,并使您更好地掌握文件备份。

请注意,文件副本的日期和时间与原始文件的日期和时间完全相同。如果-a省略该参数,则将为它们提供当前日期和时间,并且看起来像是真正的备份,只是文件大小相同。


6
难道人们不总是建议您不要分析ls
吗?

3
自从您提到find我以来,我假设您知道解析的危险ls?但是,在您的情况下,则没有必要:只需这样做for file in "$1"*; do copy -a "$file" "$file$2"; done—与通过lsor find和and 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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.