情况:
我需要一个bash脚本,它可以删除当前文件夹中除名为".rmignore"的文件之外的所有文件。这个文件可能包含相对于当前文件夹的地址,也可能包含星号(*)。例如:
1.php
2/1.php
1/*.php
我尝试过的方法:
- 我尝试使用
GLOBIGNORE
,但效果不佳。 我还尝试使用
find
和grep
,如下所示:find . | grep -Fxv $(echo $(cat .rmignore) | tr ' ' "\n")
情况:
我需要一个bash脚本,它可以删除当前文件夹中除名为".rmignore"的文件之外的所有文件。这个文件可能包含相对于当前文件夹的地址,也可能包含星号(*)。例如:
1.php
2/1.php
1/*.php
我尝试过的方法:
GLOBIGNORE
,但效果不佳。我还尝试使用find
和grep
,如下所示:
find . | grep -Fxv $(echo $(cat .rmignore) | tr ' ' "\n")
find . -type f | grep -vFf .rmignore
grep
吗?如果你有很多文件,那就相当沉重了。 - ghoti.rmignore
包含OP建议的1 / *.php
,则此grep将删除所有包含1 / *.php
的文件,这意味着如果我们在1下有一个文件,例如1/tutorialphp.txt
也将被删除。大多数情况下,grep不适用于文件操作... - George Vasiliougrep -Fv
= Fixed String,所有的 *.php
文件都不会被 grep -vF 捕获,因此将被删除。您可以在此处测试它:http://www.tutorialspoint.com/execute_bash_online.php?PID=0Bw_CjBb95KQMcHFKQlA4NTJQZnM - George Vasiliou.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
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
扩展为全局通配符。
最后,如果您想要一个更重量级的解决方案,可以使用find
和grep
:
find . -type f -not -exec grep -q -f '{}' .rmignore \; -delete
grep
。它不是一个 bash 解决方案,只依赖于 find
,而后者是相当通用的。rm
命令都可以替换为 echo rm
,以执行一种干运行,然后再进行真正的操作。 - George Vasilioufind
的退出管道到另一个命令是不好的做法。您可以使用-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;
.rmignore
文件包含目录中的文件,因此您需要在初始的 for 循环上使用 globstar
。第二,在您的 for
行末尾的 ;
是多余的;也许它是剩下的想要注释的分割行?第三,bash 有 true
和 false
作为内置,因此如果您设置了 $local rmfile=true
并稍后设置了 $rmfile=false
,您可以简单地使用 $rmfile && echo ...
。当然,这借助于更具描述性的布尔变量,如 $is_target
。 - ghotiglobstar
,谢谢!我喜欢在我的for
行末加上分号,这更多是一种风格(除非有问题)。但你怎么避免双重循环呢? - jraynalmapfile
将数组填充为排除项,然后我们遍历文件并忽略已映射的文件。其他解决方案填充一个数组,然后从中删除内容,最后rm
数组中剩余的内容。我希望这样更有效率,但是你永远不知道。 :) - ghotirsync
,则可以使用适当的rsync忽略文件将空目录复制到目标目录。在实际运行之前,首先尝试使用-n
查看它将尝试什么!或者,您可能想查看最简单的格式:
rm $(ls -1 | grep -v .rmignore)
这是另一种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