【问题标题】:Can Unix shell be used to report completion status in some manner?可以使用 Unix shell 以某种方式报告完成状态吗?
【发布时间】:2013-06-26 16:54:22
【问题描述】:

我已经看到了一些关于 SO 和外部特定命令(例如 cat)的进度条的想法。但是,我的问题似乎与标准略有不同...

目前,我在shell中使用find命令的能力,比如下面的例子:

find . -name file -exec cmd "{}" \;

“cmd”通常是一种压缩功能或释放磁盘空间的删除工具。 什么时候 ”。”非常大,这可能需要几分钟,我想要一些报告“状态”的能力。

有什么方法可以让某种类型的进度条、完成百分比,甚至打印周期(即正在工作......)直到完成?如果可能的话,我想通过添加另一个find 来避免增加执行的持续时间。有可能吗?

提前致谢。

【问题讨论】:

    标签: bash shell scripting ksh


    【解决方案1】:

    如果您安装了dialog 实用程序(),您可以轻松制作漂亮的滚动显示:

    find . -type f -name glob -exec echo {} \; -exec cmd {} \; |
    dialog --progressbox "Files being processed..." 12 $((COLUMNS*3/2))
    

    --progressbox 的参数是框的标题(可选,不能看起来像数字);文本行的高度和文本列的宽度。 dialog 有很多选项可以自定义演示文稿;以上只是为了让您入门。

    dialog 也有一个进度条,也称为“仪表”,但正如@glennjackman 在他的回答中指出的那样,您需要知道要显示进度需要做多少工作。一种方法是收集 find 命令的整个输出,计算其中的文件数,然后从累积的输出中运行所需的任务。但是,这意味着要等到 find 命令完成才能开始工作,这可能是不可取的。

    仅仅因为这是一个有趣的挑战,我想出了以下解决方案,它可能是过度设计的,因为它试图解决我能想到的所有 shell 陷阱(即使如此,它也可能遗漏了一些)。它由两个 shell 文件组成:

    # File: run.sh
    
    #!/bin/bash
    # Usage: run.sh root-directory find-tests
    #
    # Fix the following path as required
    PROCESS="$HOME/bin/process.sh"
    TD=$(mktemp --tmpdir -d gauge.XXXXXXXX)
    find "$@" -print0 |
    tee >(awk -vRS='\0' 'END{print NR > "'"$TD/_total"'"}';
          ln -s "$TD/_total" "$TD/total") |
    { xargs -0 -n50 "$PROCESS" "$TD"; printf "XXX\n100\nDone\nXXX\n"; } |
    dialog --gauge "Starting..." 7 70 
    rm -fR "$TD"
    
    # File: process.sh
    
    #!/bin/bash
    TD="$1"; shift
    TOTAL= 
    if [[ -f $TD/count ]]; then COUNT=$(cat "$TD/count"); else COUNT=0; fi
    for file in "$@"; do
      if [[ -z $TOTAL && -f $TD/total ]]; then TOTAL=$(cat "$TD/total"); fi
      printf "XXX\n%d\nProcessing file\n%q\nXXX\n" \
             $((COUNT*100/${TOTAL:-100})) "$file"
      #
      # do whatever you want to do with $file
      #
      ((++COUNT))
    done
    echo $COUNT > "$TD/count"
    

    一些注意事项:

    上面散落着很多gnu扩展。我没有列出完整的列表,但它肯定包括%q printf 格式(可能只是%s);用于 NUL 终止文件名列表的标志,以及 --tmpdir 标志到 mktemp

    run.sh 使用tee 同时计算找到的文件数(使用awk)并开始处理文件。

    xargs-n50 参数使其仅等待前 50 个文件,以避免在 find 花费大量时间未找到第一个文件时延迟启动;可能没有必要。

    awk-vRS='\0' 参数使其使用 NUL 作为行分隔符,以匹配 -print0 操作到 find(以及 -0 选项到 xargs);仅当文件路径可以包含换行符时,所有这些都是必需的。

    awk 将计数写入_total,然后我们将_total 符号链接到total,以避免在完全写入之前读取total 的非常不可能的竞争条件。符号链接是原子的,所以这样做可以保证total 要么不存在,要么完全写入。

    计算文件的总大小可能比只计算文件大小更好,尤其是在处理工作与文件大小相关的情况下(例如压缩)。那将是一个相当简单的修改。此外,使用xargs 并行执行功能也很诱人,但这需要更多的工作来协调并行进程之间已处理文件的总和。

    如果您使用的是没有dialog 的托管环境,最简单的解决方案是在有dialog 的环境中使用ssh 运行上述脚本。从 run.sh 中删除 | dialog --gauge "Starting..." 7 70,并将其放入您的 ssh 调用中:ssh user@host /path/to/run.sh root-dir find-tests | dialog --gauge "Starting..." 7 70

    【讨论】:

    • 关于文件的总大小,您的答案会如何变化?如果总大小为 GB,执行会变慢吗?这正是我一直在寻找的,但似乎我的外部管理环境不包括“对话”命令。我将不得不想出另一个解决方案。
    • 要使第二个解决方案适用于文件大小,您需要将 -print0 替换为 -printf "%s/%p\0" 之类的东西(它将打印大小和名称,用 @ 分隔987654358@ 并且仍然以NUL 终止。然后awk 调用需要类似于awk -vFS=/ -vRS='\0' "{s+=$1}END{print s}" 并且在处理循环中,您需要通过添加filename=${file#.*/} 来删除大小。您还需要必须以某种方式想出一个合理的起始总大小值。我编辑了关于dialog的答案
    • 糟糕。 filename="${file#*/}"
    • @bts:此外,还有许多其他类似对话的程序,例如,whiptail 和 tcdialog。
    • 我确实有鞭子。我假设它有一个非常相似的实现,感谢您的帮助!
    【解决方案2】:

    find 的诀窍是添加两个 -print 子句,一个在开头,然后 最后一个。然后,您使用awk(或perl)为每个更新和打印一个行计数器 独特的线。在这个例子中,我告诉 awk 打印到 stderr。

    任何重复的行都必须是我们指定的条件的结果,因此我们将其视为特殊的。 在本例中,我们只打印该行:

    find . -print -name aa\* -print |
    awk '$0 == last {
        print "" > "/dev/fd/2"
        print
        next
    }
    {
        printf "\r%d", n++ > "/dev/fd/2"
        last=$0
    }'
    

    最好让 find 只报告路径名,并从 awk 进行进一步处理, 或者只是添加另一个管道。 (因为计数器打印到标准错误,那些不会 干扰。)

    【讨论】:

      【解决方案3】:

      显然,如果您知道命令将花费多长时间运行,或者如果它可以告诉您它已完成 y 中的 x 个任务,您只能使用进度表或完成百分比。

      以下是在某项工作时显示指标的简单方法:

      #!/bin/sh
      echo "launching: $@"
      spinner() {
          while true; do
              for char in \| / - \\; do
                  printf "\r%s" "$char"
                  sleep 1
              done
          done
      }
      # start the spinner
      spinner &
      spinner_pid=$!
      # launch the command
      "$@"
      # shut off the spinner
      kill $spinner_pid
      echo ""
      

      所以,你会这样做(假设脚本名为“progress_indicator”)

      find . -name file -exec progress_indicator cmd "{}" \;
      

      【讨论】:

      • 对,那么您知道如何在不显着增加运行时间的情况下确定任务数量“y”吗?不幸的是,'code'find'code' 需要非常长的时间来枚举文件。也许'du' 命令会起作用?我会在更多研究后更新。目前,这对我来说似乎是一个很好的解决方法。我会告诉你进展如何。
      • 这似乎为它对其执行操作的每个文件打印一个进度指示器。有没有办法把它扩展到整个 find 命令?换句话说,是否可以以某种方式更改此代码,以不为找到的每个文件打印一个新的微调器,而是为整个查找操作打印?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-05
      • 2016-05-28
      • 1970-01-01
      • 2010-09-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多