除了配置文件中提到的文件,删除所有文件

5

情况:

我需要一个bash脚本,它可以删除当前文件夹中除名为".rmignore"的文件之外的所有文件。这个文件可能包含相对于当前文件夹的地址,也可能包含星号(*)。例如:

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

我尝试过的方法:

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

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


谢谢大家提供的所有答案。在我接受其中一个之前,我需要一些时间来阅读、理解、测试和比较它们。 - Stanislav Goldenshluger
那么,这些答案中有任何一个对你有帮助吗? - George Vasiliou
6个回答

1
这行代码完美地完成了工作。
find . -type f | grep -vFf .rmignore

1
那么你会为每个被考虑的文件运行一次 grep 吗?如果你有很多文件,那就相当沉重了。 - ghoti
1
如果.rmignore包含OP建议的1 / *.php,则此grep将删除所有包含1 / *.php的文件,这意味着如果我们在1下有一个文件,例如1/tutorialphp.txt也将被删除。大多数情况下,grep不适用于文件操作... - George Vasiliou
仍然不能按照 OP 的期望工作。使用 grep -Fv = Fixed String,所有的 *.php 文件都不会被 grep -vF 捕获,因此将被删除。您可以在此处测试它:http://www.tutorialspoint.com/execute_bash_online.php?PID=0Bw_CjBb95KQMcHFKQlA4NTJQZnM - George Vasiliou

1
如果我们假设.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,以执行一种干运行,然后再进行真正的操作。 - George Vasiliou
@GeorgeVasiliou,谢谢,好建议。我已经做出了改变,同时为了更清晰,改进了变量名称。 - ghoti

1
被认为将find的退出管道到另一个命令是不好的做法。您可以使用-exec-execdir后跟命令和'{}'作为文件的占位符,';'表示命令的结束。您也可以使用'+'将命令串联在一起,如果我没记错的话。
在您的情况下,您想列出目录中的所有内容,并逐个删除文件。
#!/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;

做得好。除了我担心在循环内部迭代循环之外,我只有三个琐碎的建议。第一,OP 的 .rmignore 文件包含目录中的文件,因此您需要在初始的 for 循环上使用 globstar。第二,在您的 for 行末尾的 ; 是多余的;也许它是剩下的想要注释的分割行?第三,bash 有 truefalse 作为内置,因此如果您设置了 $local rmfile=true 并稍后设置了 $rmfile=false,您可以简单地使用 $rmfile && echo ...。当然,这借助于更具描述性的布尔变量,如 $is_target - ghoti
我不知道globstar,谢谢!我喜欢在我的for行末加上分号,这更多是一种风格(除非有问题)。但你怎么避免双重循环呢? - jraynal
好的..我的答案有几个选项。第一个版本使用mapfile将数组填充为排除项,然后我们遍历文件并忽略已映射的文件。其他解决方案填充一个数组,然后从中删除内容,最后rm数组中剩余的内容。我希望这样更有效率,但是你永远不知道。 :) - ghoti

0
如果您有rsync,则可以使用适当的rsync忽略文件将空目录复制到目标目录。在实际运行之前,首先尝试使用-n查看它将尝试什么!

0

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

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

0

这是另一种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

在此处在线测试


@Станислав Гольденшлюгер 更新 - 无需临时文件 - George Vasiliou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接