【问题标题】:how to get basename in -exec of find?如何在find的-exec中获取basename?
【发布时间】:2013-08-13 01:23:19
【问题描述】:

我无法使以下脚本(它是较大备份脚本的一部分)正常工作:

BACKUPDIR=/BACKUP/db01/physical/incremental # Backups base directory
FULLBACKUPDIR=$BACKUPDIR/full # Full backups directory
INCRBACKUPDIR=$BACKUPDIR/incr # Incremental backups directory
KEEP=5 # Number of full backups (and its incrementals) to keep
...
FIRST_DELETE=`expr $KEEP + 1` # add one to the number of backups to keep, this will be the first deleted
FILE0=`ls -ltr $FULLBACKUPDIR | awk '{print $9}' | tail -$FIRST_DELETE | head -1` # search for the first backup to be deleted
...
find $FULLBACKUPDIR -maxdepth 1 -type d ! -newer $FULLBACKUPDIR/$FILE0 -execdir echo "removing: "$FULLBACKUPDIR/$(basename {}) \; -execdir bash -c 'rm -rf $FULLBACKUPDIR/$(basename {})' \; -execdir echo "removing: "$INCRBACKUPDIR/$(basename {}) \; -execdir bash -c 'rm -rf $INCRBACKUPDIR/$(basename {})' \;

所以 find 工作正常,它自己会输出如下内容:

/BACKUPS/db01/physical/incremental/full/2013-08-12_17-51-28
/BACKUPS/db01/physical/incremental/full/2013-08-12_17-51-28
/BACKUPS/db01/physical/incremental/full/2013-08-12_17-25-07

我想要的是 -exec 来回显显示正在删除的内容的行,然后从两个目录中删除该文件夹。

我尝试了各种方法来获取基本名称,但似乎没有任何效果。我明白了:

removing: /BACKUPS/mysql/physical/incremental/full/"/BACKUPS/mysql/physical/incremental/full/2013-08-12_17-51-28"
removing: /BACKUPS/mysql/physical/incremental/incr/"/BACKUPS/mysql/physical/incremental/full/2013-08-12_17-51-28"
removing: /BACKUPS/mysql/physical/incremental/full/"/BACKUPS/mysql/physical/incremental/full/2013-08-12_17-25-07"

当然,这些文件夹并没有被删除,因为它们不存在,只是因为 -f 选项而静默失败。如果我删除 -f 我会在每个 rm 上收到“找不到”错误。

我该如何做到这一点?因为备份和部分备份可能存储在不同的存储系统中,所以我真的需要能够获取文件夹名称以在任何已知路径中使用。

【问题讨论】:

    标签: bash rm


    【解决方案1】:

    首先运行$(basename {}),使removing: "$INCRBACKUPDIR/$(basename {}) 变为removing: "$INCRBACKUPDIR/{},然后替换完成{}

    一种解决方法可能是将其通过管道传输到 bash:

    -exec echo "echo \"removing: \\\"$INCRBACKUPDIR/\$(basename {})\\\"\" | bash" \;
    

    【讨论】:

      【解决方案2】:

      这里有很多破损。

      • 所有大写变量都是约定的 env var,不应在脚本中使用。
      • 使用旧的反引号代替$()
      • 解析ls (!)的输出
      • 解析ls -l的输出(!!!)
      • 扩展已知包含不带全引号的路径的变量。

      绝对需要以改善这一点,正确地-exec bash,例如

      -execdir bash -c 'filepath="$1" ; base=$(basename "$filepath") ; echo use $filepath and $base here' -- {} \;
      

      但是这个怎么样:

      #!/usr/bin/env bash
      
      backup_base=/BACKUP/db01/physical/incremental
      full_backup="$backup_base"/full
      incremental_backup="$backup_base"/incr
      keep=5
      rm=echo
      
      let n=0
      while IFS= read -r -d $'\0' line ; do
          file="${line#* }"
          if [[ $n -lt $keep ]] ; then
                  let n=n+1
                  continue
          fi
          base=$(basename "$file")
          echo "removing: $full_backup/$base"
          "$rm" -rf -- "$full_backup"/"$base"
          echo "removing: $incremental_backup/$base"
          "$rm" -rf -- "$incremental_backup"/"$base"
      done < <(find "$full_backup" -maxdepth 1 -printf '%T@.%p\0' 2>/dev/null | sort -z -r -n -t. -k1,2)
      

      遍历备份目录下的文件和目录并跳过前 5 个最新的。从与其余名称匹配的完整和增量 dirs 文件中删除。

      这是一个本质上安全的版本,当然除了定时攻击。

      我已将rm 定义为echo 以避免意外删除;确定正确后,将其换回rm 进行实际删除。

      【讨论】:

      • 为了节省时间,我选择了第一个解决方案,因为删除失败会在备份建立时导致存储问题。 find $FULLBACKUPDIR -maxdepth 1 -type d ! -newer "$FULLBACKUPDIR/$FILE0" \ -execdir bash -c 'base=$(basename "$2") ; echo "removing: $1/$base"' -- $FULLBACKUPDIR {} \; \ -execdir bash -c 'base=$(basename "$2") ; rm -rf $1/$base' -- $FULLBACKUPDIR {} \; \ -execdir bash -c 'base=$(basename "$2") ; echo "removing: $1/$base"' -- $INCRBACKUPDIR {} \; \ -execdir bash -c 'base=$(basename "$2") ; rm -rf $1/$base' -- $INCRBACKUPDIR {} \;
      • 我一定会回去研究更健壮的 WHILE 循环方法并在有时间时更新脚本。感谢您让我走上正轨。
      • 实际上-execdir 方法是首选,因为它不会遭受符号链接攻击,可以允许本地用户删除具有调用脚本权限的任意文件。有关 find 的安全问题,请参阅 GNU 文档。不过,要执行目录的部分可以是单独的脚本
      猜你喜欢
      • 2017-08-01
      • 2020-02-14
      • 1970-01-01
      • 1970-01-01
      • 2014-03-16
      • 2015-10-28
      • 1970-01-01
      • 1970-01-01
      • 2019-09-07
      相关资源
      最近更新 更多