【问题标题】:Deleting all files except ones mentioned in config file删除除了配置文件中提到的所有文件
【发布时间】:2018-11-17 00:31:33
【问题描述】:

情况:

我需要一个 bash 脚本来删除当前文件夹中的所有文件,除了名为“.rmignore”的文件中提到的所有文件。此文件可能包含与当前文件夹相关的地址,也可能包含星号 (*)。例如:

1.php
2/1.php
1/*.php

我的尝试:

  • 我尝试使用GLOBIGNORE,但效果不佳。
  • 我还尝试将findgrep 一起使用,如下所示:

    find . | grep -Fxv $(echo $(cat .rmignore) | tr ' ' "\n")

【问题讨论】:

  • 谢谢你们,所有这些答案。我需要一些时间阅读、理解、测试和比较它们,然后才能接受。
  • 嗯,这些答案对你有帮助吗?

标签: linux bash shell


【解决方案1】:

find 的出口通过管道传递给另一个命令被认为是不好的做法。您可以使用-exec-execdir 后跟命令,'{}' 作为文件的占位符,';' 表示命令的结束。您还可以使用'+' 将命令通过管道传输到 IIRC。

在你的情况下,你想列出一个目录的所有竞争,并一个一个地删除文件。

#!/usr/bin/env bash

set -o nounset
set -o errexit
shopt -s nullglob # allows glob to expand to nothing if no match
shopt -s globstar # process recursively current directory

my:rm_all() {
    local ignore_file=".rmignore"
    local ignore_array=()
    while read -r glob; # Generate files list
    do
        ignore_array+=(${glob});
    done < "${ignore_file}"
    echo "${ignore_array[@]}"

    for file in **; # iterate over all the content of the current directory
    do
        if [ -f "${file}" ]; # file exist and is file
        then
            local do_rmfile=true;
            # Remove only if matches regex
            for ignore in "${ignore_array[@]}"; # Iterate over files to keep
            do
                [[ "${file}" == "${ignore}" ]] && do_rmfile=false; #rm ${file};
            done

            ${do_rmfile} && echo "Removing ${file}"
        fi
    done
}

my:rm_all;

【讨论】:

  • 做得很好。除了我担心在循环中迭代循环之外,我只有三个挑剔的建议。 1st,OP 的 .rmignore 文件包含目录中的文件,因此您需要在初始 for 循环中使用 globstar。第二,; 在你的 for 行末尾是多余的;也许这是拆分需要评论的行的剩余部分?第三,bash有truefalse作为内置函数,所以如果你local rmfile=true之后设置rmfile=false,你可以简单的$rmfile &amp;&amp; echo ...。当然,这适用于更具描述性的布尔变量,例如$is_target
  • 我不知道globstar,谢谢!我喜欢在for 行的末尾添加一个半栏,这更像是一种风格(除非这里有问题)。你会如何避免做一个双循环呢?
  • 嗯..我的回答有几个选项。在第一个版本中,使用mapfile 来填充具有排除项的数组,然后我们逐步检查文件并忽略已映射的文件。其他解决方案填充一个数组,然后从中删除东西,然后 rm 数组中剩余的任何内容。我希望这会更有效率,但是嘿,你永远不知道。 :)
【解决方案2】:

如果我们假设 .rmignore 中的所有文件的名称中都不包含换行符,则以下内容可能就足够了:

# Gather our exclusions...
mapfile -t excl < .rmignore

# Reverse the array (put data in indexes)
declare -A arr=()
for file in "${excl[@]}"; do arr[$file]=1; done

# Walk through files, deleting anything that's not in the associative array.
shopt -s globstar
for file in **; do
  [ -n "${arr[$file]}" ] && continue
  echo rm -fv "$file"
done

注意:未经测试。 :-) 此外,Bash 4 引入了关联数组。

另一种方法可能是用整个文件列表填充数组,然后删除排除项。如果您要处理数十万个文件,这可能不切实际。

shopt -s globstar
declare -A filelist=()

# Build a list of all files...
for file in **; do filelist[$file]=1; done

# Remove files to be ignored.
while read -r file; do unset filelist[$file]; done < .rmignore

# Annd .. delete.
echo rm -v "${!filelist[@]}"

也未经测试。

警告:rm 风险自负。可能含有坚果。保留备份。

我注意到这些解决方案都不会处理您的.rmignore 文件中的通配符。为此,您可能需要一些额外的处理...

shopt -s globstar
declare -A filelist=()

# Build a list...
for file in **; do filelist[$file]=1; done

# Remove PATTERNS...
while read -r glob; do
  for file in $glob; do
    unset filelist[$file]
  done
done < .rmignore

# And remove whatever's left.
echo rm -v "${!filelist[@]}"

而且..你猜对了。未经测试。这取决于$f 扩展为一个全局对象。

最后,如果您想要更重的解决方案,您可以使用findgrep

find . -type f -not -exec grep -q -f '{}' .rmignore \; -delete

这将为正在考虑的每个文件运行grep。而且它不是一个 bash 解决方案,它只依赖于 find,这是非常普遍的。

请注意,如果您的文件包含换行符,所有这些解决方案都有出错的风险。

【讨论】:

  • 不错的解决方案。每个rm 命令都可以替换为echo rm,以在实拍前进行一种试运行。
  • @GeorgeVasiliou,谢谢,很好的建议。为了清晰起见,我进行了更改,同时改进了变量名称。
【解决方案3】:

这条线完美地完成了这项工作

find . -type f | grep -vFf .rmignore

【讨论】:

  • 所以你将为每个正在考虑的文件运行一次grep?如果你有很多文件,那就相当沉重了。
  • 如果 .rmignore 包含 1/*.php 就像 OP 建议的那样,这个 grep 将删除所有包含 1/*.php 的文件,这意味着如果我们有一个像 1/tutorialphp.txt 这样的小于 1 的文件也将被删除。 grep 大部分时间都不适合文件操作...
  • 仍然无法按 OP 的预期工作。使用 grep -Fv = Fixed String ,所有 *.php 文件将不会被 grep -vF 捕获,因此将被删除。你可以在这里自己测试:tutorialspoint.com/…
【解决方案4】:

如果你有rsync,你也许可以将一个空目录复制到目标目录,并使用合适的 rsync 忽略文件。先用-n试试,看看它会尝试什么,然后再真正运行它!

【讨论】:

    【解决方案5】:

    这是另一个 bash 解决方案,在我的测试中似乎可以正常工作:

    while read -r line;do 
    exclude+=$(find . -type f -path "./$line")$'\n'
    done <.rmignore
    
    echo "ignored files:"
    printf '%s\n' "$exclude"
    echo "files to be deleted"
    echo rm $(LC_ALL=C sort <(find . -type f) <(printf '%s\n' "$exclude") |uniq -u )  #intentionally non quoted to remove new lines
    

    Test it online here

    【讨论】:

    • @Станислав Гольденшлюгер 已更新 - 没有临时文件
    【解决方案6】:

    或者,您可能想查看最简单的格式:

    rm $(ls -1 | grep -v .rmignore)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-27
      • 1970-01-01
      • 2021-11-10
      相关资源
      最近更新 更多