Shell脚本中的while read循环只执行一次

11
我正在编写一个shell脚本,批处理从我的相机中下载的.mov文件,通过Handbrake来节省高清空间。该脚本使用'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 文件,因此循环结构是正确的。
我认为我的问题在这个stackoverflow线程上有部分答案。但该解决方案特定于ssh,并不能解决一般问题。似乎与子进程使用stdin有关,但我不完全理解其工作原理。
有什么建议吗?
我使用的是OSX 10.6。

${SHELL} == '/bin/bash',一个人会认为是这样吧? - johnsyweb
不知道 $* 应该是什么,无法回答这个问题。它应该是文件或目录列表吗?还是当前目录?看起来你可能只是在使用错误的 file 参数和错误的 shell 特殊参数(例如,使用 file "$@" 而不是 "$*")。 - Jens
"$*" 很明显是错误的;你肯定想要使用 "$@" - Charles Duffy
6个回答

3

参考这个答案的做法:现在我会将空内容回显到HandbrakeCLI中,以确保它不使用与我的脚本相同的标准输入:

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

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

...并且它按照预期的方式正常工作。


2

一个可能的方案是使用安全查找

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

我尝试运行这个程序,但是出现了一个错误:“在未预期的符号`<'附近的语法错误”。我应该注意到我不理解前两行的含义,所以我无法自己修复它。 - beibei2
请提供脚本上下文中 'echo $0' 的输出结果。或者,这是哪个版本的 Bash (bash --version)?由于您可能不在 bash 上运行,请注意仅返回翻译后的文本内容。 - sehe
bash 版本:GNU bash,版本 3.2.48(1)-release (x86_64-apple-darwin10.0) echo $0:/Volumes/Macintosh HD/Users/N/Unix/bin/nsqueezemovs - beibei2
@beibei 你的脚本是以 #!/bin/sh 还是 #!/bin/bash 开头的?只有使用后者才能让 <() 正常工作。 - Charles Duffy

1

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

防止子进程(HandbrakeCLI)导致父脚本退出

以下是适用于我的代码:

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

1
你可能正在使用一个名为'-e'的shell选项(遇到错误即退出)
尝试:
set +e

1
设置+e没有任何区别。Handbrake在while-read循环中仅对第一个文件执行一次,然后跳出循环并执行脚本的其余部分。我认为问题与错误无关,因为Handbrake成功完成了任务。 - beibei2
你能否尝试在子shell中执行Handbrake步骤(Handbrake)? - sehe
我尝试在子shell中运行Handbrake,但对我遇到的问题没有影响。我尝试将整个循环放入子shell中,也尝试仅将调用Handbrake的部分放入子shell中。两种方式都得到了相同的结果。 - beibei2

0

考虑直接使用find调用shell,而不是在while循环中包装find

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

谢谢,Josh。你有机会解释一下最后一行的意思吗? - beibei2
当然可以!这是一种干净的方式,将参数传递给内联脚本。考虑 sh -c 'echo "$1"' _ "hello,world"sh 将运行基本的内联脚本 echo "$1",并将 _hello,world 作为其参数传递。按照惯例,第0个参数应该保存命令本身的路径名,在这种情况下没有意义,所以 _ 只是一个无用的占位符。希望这有所帮助! - Josh Cartwright

0

我有同样的问题。我认为HandbrakeCLI正在消耗stdin。虽然我没有我的实际系统在这里,但我的测试显示这可能是发生的情况。

"output"是一个包含数字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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接