【问题标题】:Shell script while read loop executes only once读取循环时的Shell脚本仅执行一次
【发布时间】:2011-07-29 19:06:13
【问题描述】:

我正在编写一个 shell 脚本,通过Handbrake 批量处理相机上的 .mov 文件,以节省高清空间。该脚本使用“find”搜索目录,然后在找到的每个 .mov 文件上运行 Handbrake,将结果文件的创建日期与源文件的“touch”日期匹配。

我最初是通过 for 循环来实现的:

for i in $(find "$*" -iname '*.mov') ; do
  ~/Unix/HandbrakeCLI --input "$i" --output "$i".mp4 --preset="Normal"
  touch -r "$i" "$i".mp4
done

这可行,但如果输入文件的文件名中有空格,则会失败。所以我尝试了一个 while 循环:

find "$*" -iname '*.mov' | while read i ; do
  ~/Unix/HandbrakeCLI --input "$i" --output "$i".mp4 --preset="Normal"
  touch -r "$i" "$i".mp4
done

这个循环的问题是它对目录中的第一个文件起作用,然后退出循环。请注意,如果我在 while 循环的主体中替换为“echo $i”,它会打印目录中的所有 .mov 文件,因此循环结构正确。

我相信我在this stackoverflow thread 上的问题得到了部分答案。但是解决方案是特定于 ssh 的,并不能解决一般问题。似乎与子进程使用标准输入有关,但我不完全理解这是如何工作的。

有什么建议吗?

我使用的是 OSX 10.6

【问题讨论】:

  • ${SHELL} == '/bin/bash',一个假设?
  • 如果不知道 $* 应该是什么,就无法回答这个问题。它应该是文件或目录的列表吗?还是当前目录?看起来很像您只是使用了错误的 file 参数和错误的 shell 特殊参数(例如,使用 file "$@" 而不是 "$*")。
  • "$*" 肯定是错的;你肯定想要"$@"

标签: shell loops for-loop while-loop


【解决方案1】:

取自 this answer:我现在在 HandbrakeCLI 中不回显任何内容,以确保它没有使用与我的脚本相同的标准输入:

find . -name "*.mkv" | while read FILE
do
    echo "" | handbrake-touch "$FILE"

    if [ $? != 0 ]
    then
        echo "$FILE had problems" >> errors.log  
    fi
done

...它按预期/预期工作。

【讨论】:

    【解决方案2】:

    一种可能性是使用safe find

    while IFS= read -r -d '' -u 9
    do
        ~/Unix/HandbrakeCLI --input "$REPLY" --output "$REPLY".mp4 --preset="Normal"
        touch -r "$REPLY" "$REPLY".mp4
    done 9< <( find "$@" -type f -print0 )
    

    这应该是 POSIX 兼容的,但只有在 HandbrakeCLItouch 从标准输入中读取并且没有文件名包含换行符时才有效:

    find "$@" -type f -print0 | while IFS= read -r
    do
        ~/Unix/HandbrakeCLI --input "$REPLY" --output "$REPLY".mp4 --preset="Normal"
        touch -r "$REPLY" "$REPLY".mp4
    done
    

    【讨论】:

    • 我尝试运行它并得到一个错误:“意外标记附近的语法错误 `
    • 这意味着您可能不在 bash 上。请在脚本上下文中报告“echo $0”的输出?或者,这是什么版本的 bash (bash --version)
    • bash 版本:GNU bash,版本 3.2.48(1)-release (x86_64-apple-darwin10.0) echo $0: /Volumes/Macintosh HD/Users/N/Unix/bin/nsqueezemovs
    • @beibei 您的脚本是以#!/bin/sh 还是#!/bin/bash 开头的? &lt;() 必须是后者才能工作。
    【解决方案3】:

    您可能正在使用 shellopt '-e' 运行(出错时退出)

    试试

    set +e
    

    【讨论】:

    • 设置 +e 没有任何区别。 Handbrake 在 while-read 循环中的第一个文件上执行一次,然后跳出循环并执行脚本的其余部分。我不认为问题与错误有关,因为手刹成功完成。
    • 您可以尝试在子外壳中执行手刹步骤吗 (Handbrake)
    • 我尝试在子外壳中运行手刹,但它对我遇到的问题没有影响。我尝试对整个循环进行 subshel​​ling,也只是对手刹的调用。两种方式的结果相同。
    【解决方案4】:

    这个链接解决了我在 Ubuntu Linux 11.04 上的问题。

    Preventing a child process (HandbrakeCLI) from causing the parent script to exit

    下面是适合我的代码:

    ls -t *.mpeg | tail -n +2 | while read name
    do echo "$name" ; in="$name" ; out=coded/"`echo $name | sed 's/.mpeg$/.mkv/'`"
    echo "" | HandBrakeCLI -i "$in" -o "$out" -a '1,2' ; mv -v "$name" trash/"$name"
    done
    

    【讨论】:

      【解决方案5】:

      我也遇到了同样的问题。我认为 HandbrakeCLI 正在消耗标准输入。我这里没有我的实际系统,但我的测试表明这可能正在发生。

      “输出”是一个编号为 1-10 的文件,每个文件占一行。

      while read line ; do echo "MAIN: $line"; ./consumer.sh; done < output
      

      consumer.sh:

      #!/bin/sh
      
      while read line ; do
        echo "CONSUMER: $line"
      done
      

      结果:

      MAIN: 1
      CONSUMER: 2
      CONSUMER: 3
      CONSUMER: 4
      CONSUMER: 5
      CONSUMER: 6
      CONSUMER: 7
      CONSUMER: 8
      CONSUMER: 9
      CONSUMER: 10
      

      更改外部 while 循环将解决问题:

      while read line ; do echo "MAIN: $line"; ./consumer.sh < /dev/null; done < output
      

      结果:

      MAIN: 1
      MAIN: 2
      MAIN: 3
      MAIN: 4
      MAIN: 5
      MAIN: 6
      MAIN: 7
      MAIN: 8
      MAIN: 9
      MAIN: 10
      

      【讨论】:

        【解决方案6】:

        考虑只使用find 调用shell,而不是将find 包装在while 循环中。

        find "$@" -iname '*.mov' -exec sh -c '
            ~/Unix/HandbrakeCLI --input "$1" --output "$1".mp4 --preset="Normal"
            touch -r "$1" "$1".mp4
        ' _ {} ';'
        

        【讨论】:

        • 谢谢乔希。你有机会详细说明最后一行的含义吗?
        • 当然!它以一种简洁的方式将参数传递给内联脚本。考虑sh -c 'echo "$1"' _ "hello,world"sh 将运行基本的内联脚本 echo "$1" 作为其参数传递 _hello,world。按照惯例,第 0 个参数应该包含命令本身的路径名,在这种情况下这没有意义,所以 _ 只是一个无用的占位符。希望对您有所帮助!
        猜你喜欢
        • 2018-09-06
        • 2014-06-01
        • 2015-04-02
        • 2012-04-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-18
        • 1970-01-01
        相关资源
        最近更新 更多