如何在父 shell 中使用子 shell 中的值

如何在父 shell 中使用子 shell 中的值

我不习惯 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 版本的findsortcut

答案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"

相关内容