我不习惯 Linux 脚本编写,这是我第一次使用它,所以我遇到了以下问题:
代码:
while [ $pct -gt 80 ]; do
flag=1;
ls -tr | while read file; do
if [[ $file =~ .+\.log[0-9]+ ]]; then
printf "File deleted:\n";
stat $file;
rm -r $file;
flag=1;
break;
else
flag=0;
fi;
done;
if [ $flag -eq 0]; then
break;
fi;
pct= # get the new pct;
done;
该操作是删除上述正则表达式捕获的某些日志文件,按照最旧文件的顺序首先,因此我使用 ls -tr。我使用 while 循环迭代文件列表,如果有任何文件与给定的正则表达式匹配,我将删除它。每次删除后,我都会检查所使用的应用程序文件系统的百分比,如果它大于 80%(如外部 while 循环条件所示),我会重复该过程。
现在,即使删除文件后,使用的百分比也不会低于 80%,即使没有留下具有给定正则表达式模式的文件,并且我无法删除同一文件夹中的其他剩余文件。因此,它进入无限循环,因此我尝试使用标志变量来打破这种情况下的无限循环。但是,由于我使用管道来迭代文件,因此它成为子 shell 或子进程,并且父进程中的标志变量不可用(子 shell 中标志的更新值不会反映在父 shell 中) (这是我在一篇有关管道的文章中读到的内容)因此无限循环永远不会中断。任何人都可以建议解决此问题或替代逻辑吗?
答案1
用zsh
而不是bash
:
while
pct= # get the new pct
(( pct > 80 )) &&
oldest_log_file=( *.log<->(N.Om[1]) ) &&
(( $#oldest_log_file ))
do
print -r Removing $oldest_log_file
rm -f -- $oldest_log_file
done
或者:
log_files_from_oldest_to_newest=( *.log<->(N.Om) )
while
pct= # get the new pct
(( pct > 80 )) &&
(( $#log_files_from_oldest_to_newest ))
do
print -r Removing $log_files_from_oldest_to_newest[1]
rm -f -- $log_files_from_oldest_to_newest[1]
shift 1 log_files_from_oldest_to_newest
done
或者:
zmodload zsh/stat
for log_file in *.log<->(N.Om); do
pct= # get the new pct
(( pct > 80 )) || break
stat -F %FT%T%z -LH s -- $log_file || continue
print -r Removing $log_file of size $s[size] last modified on $s[mtime]
rm -f -- $log_file
done
答案2
解析 ls 非常脆弱,而且确实不是一个好主意。但是,如果您确定您的文件名永远不会包含空格、制表符、换行符(假设未修改的$IFS
),也不会包含全局模式运算符(至少是*
, ),并且不会以 和 开头,不是类型?
[
-
目录,你可以这样做:
#!/bin/bash
pct= # get the current pct
## are there any matching files?
files=( $(ls -tr *.log[0-9]* 2>/dev/null) );
## While pct is above the threshold and there is at least
## one file in the files array
while [[ $pct -gt 80 && ${#files[@]} -gt 0 ]]; do
printf "Deleting file:\n%s\n" "$(stat -- "${files[0]}")"
rm -- "${files[0]}"
## repopulate the files array
files=( $(ls -tr *.log[0-9]* 2>/dev/null) );
pct=85 # get the new pct;
done
这里的想法是将文件名存储在一个数组中,并在每次删除后重新定义该数组。然后,我们让循环在两个条件下运行:“还有剩余文件吗?”和“$pct 是否低于 80?”,因此当任一条件不再成立时,它将停止。
注意事项1:这假设您没有名为 之类的文件foo.log12bar
,即您想要查看.log
包含后跟数字的字符串的所有文件,并且不想避免第一个数字后包含非数字字符的文件名。
注意事项2:如开头所述,对于不常用名称的文件名,这将失败。请参阅此处,了解为什么解析 的输出ls
几乎总是一个坏主意:
答案3
尝试这样的事情:
#!/bin/bash
dir="/path/to/dir"
# read the matching files into array "$files"
mapfile -d '' files < <(find "$dir" -maxdepth 1 -type f -regex '.*\.log[0-9]+' -print0)
for f in "${files[@]}"; do
# get the current percentage used of the filesystem
pct=$(df --output=pcent "$dir" | awk -F'[ %]' 'NR==2 {print $2}')
[ "$pct" -lt 80 ] && break
rm "$f"
done
这将继续一次删除一个匹配的文件,直到文件系统的当前使用百分比降至 80% 以下或者直到不再有匹配的文件名(以先到者为准)。
请注意,这需要 GNUdf
选项--output
。
对于非 GNU df
,以下内容将起作用如果设备名称(“文件系统”列)不包含任何空格或%
字符:
pct=$(df "$dir" | awk -F'[[:space:]]+|%' 'NR==2 {print $5}')
如果安装点(“安装于”列)确实包含空格,但不包含字符%
,则可以使用类似以下内容:
pct=$(df "$dir" | awk '
NR==2 {
for (i=NF; i > 0; i--) { if ($i ~ /%/) {break} };
sub("%","",$i);
print $i
}')
(是的,从df
的输出中提取数据比乍一看更难,原因几乎与解析 ls 是个坏主意)
如果您需要按时间戳对文件名进行排序(这样您可以先删除最旧的文件),您可以使用我的回答中描述的方法Shell 脚本将最旧的文件从一个目录移动到另一个目录。那会是这样的:
mapfile -d '' files < <(
find "$dir" -maxdepth 1 \
-type f \
-regex '.*\.log[0-9]+' \
-printf '%C@\t%p\0' |
sort -z -k1,1 -r -n |
cut -z -f2
)
请注意,这还需要 GNU 版本的find
、sort
和cut
。
答案4
正如您从其他很好的答案中注意到的那样,有很多不同的方法可以完成您正在尝试的事情,因此我不会再抛出另一个实现。
暂时忽略处理ls
输出存在缺陷。
问题的核心似乎是如何让变量值保持在循环之外。因此,我认为这就是您正在寻找的:
flag=foo
while read file; do
echo "do stuff with $file"
flag=bar
done < <(ls -tr) # preferably do something else to get your list
echo "$flag"