读取循环时的Shell脚本仅执行一次

Posted

技术标签:

【中文标题】读取循环时的Shell脚本仅执行一次【英文标题】:Shell script while read loop executes only once 【发布时间】: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 *** thread 上的问题得到了部分答案。但是解决方案是特定于 ssh 的,并不能解决一般问题。似乎与子进程使用标准输入有关,但我不完全理解这是如何工作的。

有什么建议吗?

我使用的是 OSX 10.6

【问题讨论】:

$SHELL == '/bin/bash',一个假设? 如果不知道 $* 应该是什么,就无法回答这个问题。它应该是文件或目录的列表吗?还是当前目录?看起来很像您只是使用了错误的 file 参数和错误的 shell 特殊参数(例如,使用 file "$@" 而不是 "$*")。 "$*" 肯定是错的;你肯定想要"$@" 【参考方案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 个参数应该包含命令本身的路径名,在这种情况下这没有意义,所以 _ 只是一个无用的占位符。希望对您有所帮助!

以上是关于读取循环时的Shell脚本仅执行一次的主要内容,如果未能解决你的问题,请参考以下文章

shell脚本并发

菜鸟一位想写一个linux shell 脚本,执行次脚本回显数字,执行第一次回显1,执行第二次显示2,以此类推...

ffmpeg在shell循环中只执行一次问题

shell脚本while用法

shell脚本实现转圈、进度条等效果

2018-07-26 第三十一次课