从 find -exec 操作 {} 返回字符串

Nir*_*Nir 4 scripting bash find shell-script

我希望尽可能高效,以防有很多文件。我想要的是重命名我找到的所有文件并删除它们的后缀。

例如:

[/tmp] $ ls -l
a.log
b.log
c.tmp
[/tmp] $ find /tmp -name "*.log" -type f -exec mv {} {%.*} \;
[/tmp] $ ls -l
a
b
c.tmp
Run Code Online (Sandbox Code Playgroud)

这不起作用。如果它是一个普通的 bash 变量,它${var%.*}会一直返回var到最后一个..

Sté*_*las 7

启动 shell 以使用 shell 参数扩展运算符:

find ~/tmp -name '*.log' -type f -exec sh -c '
  for file do
    mv -i -- "$file" "${file%.*}"
  done' sh {} +
Run Code Online (Sandbox Code Playgroud)

请注意,您不希望在/tmp其他人可写的目录或任何其他人可写的目录上执行此操作,因为这会允许恶意用户让您重命名.log文件系统¹ 上的任意文件(或将文件移动到任何目录²)。

通过一些findmv实现,您可以使用find -execdirmv -T使其更安全:

find /tmp -name '*.log' -type f -execdir sh -c '
  for file do
    mv -Ti -- "$file" "${file%.*}"
  done' sh {} +
Run Code Online (Sandbox Code Playgroud)

或者使用rename(perl 变体)只会执行rename()系统调用,因此不要尝试将文件移动到其他文件系统或目录...

find /tmp -name '*.log' -type f -execdir rename 's/\.log$//' {} +
Run Code Online (Sandbox Code Playgroud)

或者做整个事情perl

perl -MFile::Find -le '
  find(
    sub {
      if (/\.log\z/) {
        $old = $_;
        s/\.log\z//;
        rename($old, $_) or warn "rename $old->$_: $!\n"
      }
    }, @ARGV)' ~/tmp
Run Code Online (Sandbox Code Playgroud)

但请注意,perl's Find::File(与 GNU 不同find)不会执行安全的目录遍历³,因此您也不想这样做/tmp


笔记。

¹攻击者可以创建一个/tmp/. /auth.log文件,并在find找到它和mv移动它之间(并且该窗口可以很容易地变得任意大)用"/tmp/. "符号链接替换目录以/var/log导致/var/log/auth.log被重命名为/var/log/auth

² 更糟糕的是,攻击者可以创建一个/tmp/foo.log恶意软件crontab/tmp/foo一个符号链接,/etc/cron.d并让你将该 crontab 移动 /etc/cron.d. 这是与不确定性mv(也适用于cpln至少),其可以同时移动到迁入。GNUmv用它的-t( into ) 和-T( to ) 选项修复它。

³File::Find通过执行遍历目录chdir("/tmp"); read content; chdir("foo") ...; chdir("bar"); chdir("../..")...。因此,有人可以创建一个/tmp/foo/bar目录,并在适当的时候,它重命名为/tmp/bar这样chdir("../..")将土地你/


小智 6

这是一行:

find /tmp -name "*.log" -type f -exec sh -c 'f="{}"; mv "$f" "${f%.*}"' \;
Run Code Online (Sandbox Code Playgroud)

它启动一个 shell,将 {} 分配给 shell 内的适当变量,然后使用 shell 语法应用字符串操作。