读取循环时的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 兼容的,但只有在 HandbrakeCLI
和 touch
从标准输入中读取并且没有文件名包含换行符时才有效:
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
开头的? <()
必须是后者才能工作。【参考方案3】:
您可能正在使用 shellopt '-e' 运行(出错时退出)
试试
set +e
【讨论】:
设置 +e 没有任何区别。 Handbrake 在 while-read 循环中的第一个文件上执行一次,然后跳出循环并执行脚本的其余部分。我不认为问题与错误有关,因为手刹成功完成。 您可以尝试在子外壳中执行手刹步骤吗 (Handbrake
)
我尝试在子外壳中运行手刹,但它对我遇到的问题没有影响。我尝试对整个循环进行 subshelling,也只是对手刹的调用。两种方式的结果相同。【参考方案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脚本仅执行一次的主要内容,如果未能解决你的问题,请参考以下文章