在Bash for循环中,我可以将上一次迭代的stdout读为stdin吗?

时间:2019-01-29 10:12:55

标签: bash loops imagemagick pipe

我有一个bash oneliner,通过同一ImageMagick操作多次将一个图像传递到管道。我想将其作为Bash循环执行,最好不要使用临时文件。

一个班轮是:cat in.jpg | convert -quality 1 - jpg:- | convert -quality 1 - jpg:- | convert -quality 1 - jpg:- > out.jpg

此功能也适用:(convert -quality 1 - jpg:- | convert -quality 1 - jpg:- | convert -quality 1 - jpg:-) > out.jpg < in.jpg

convert命令中,-表示“从stdin读取”,而jpg:-表示“以JPEG格式输出到stdout”。

我尝试做cat in.jpg | for x in {1..3}; do convert -quality 1 - jpg:-; done > out.jpg(for x in {1..3}; do convert -quality 1 - jpg:-; done) < in.jpg > out.jpg,但都给出相同的错误。

我希望获得对其应用了所有3个操作的输出文件,但是我反而得到仅对其应用了一个操作和以下错误的输出文件,这意味着第一次迭代必须读取in.jpg文件,但是以下迭代在stdin上什么都没有:

convert: no decode delegate for this image format `' @ > error/constitute.c/ReadImage/501.
convert: no images defined `jpg:-' @ error/convert.c/ConvertImageCommand/3210.
convert: no decode delegate for this image format `' @ error/constitute.c/ReadImage/501.
convert: no images defined `jpg:-' @ error/convert.c/ConvertImageCommand/3210.

(我通过运行(for x in 90 30 1; do convert -quality $x - jpg:-; done) < in.jpg > out.jpg来确认这只是接触输出文件的第一个迭代

5 个答案:

答案 0 :(得分:4)

您可以定义一个递归函数:

rec_convert () {
  n=$1
  if [ $n -eq 0 ]; then
    cat
  else
    convert -quality 1 - jpg:- | rec_convert $((n - 1))
  fi
}

rec_convert 3 < in.jpg > out.jpg  

答案 1 :(得分:2)

这是一个丑陋的结构(我不喜欢eval),但可以解决您的问题:

cmd=()
for i in {1..3}
do
    cmd+=("convert" "-quality" "1" "-" "jpg:-" "|")
done
unset "cmd[${#cmd[@]}-1]"

eval "< in.jpg ${cmd[@]} > out.jpg"

您创建一个名为cmd的数组。然后,使用循环将命令的组件和管道符号添加到数组。创建此数组后,删除最后一个元素(不需要额外的管道)。最后,您eval包含输入重定向,创建的命令行和输出重定向的问题。

需要eval,因为否则,|被认为是文字,不会创建管道,而是作为弓形传递到convert

答案 2 :(得分:1)

类似的事情似乎起作用:

#!/bin/bash

cmd="cat input.jpg "
for ((iter=0;iter<15;iter++)) do
   cmd+=" | convert jpg:- -quality 50 jpg:- "
done
echo $cmd
bash -c "$cmd" > result.jpg

已执行的实际命令的调试输出为:

cat input.jpg | convert jpg:- -quality 50 jpg:- | convert jpg:- -quality 50 jpg:- | convert jpg:- -quality 50 jpg:- | convert jpg:- -quality 50 jpg:- | convert jpg:- -quality 50 jpg:- | convert jpg:- -quality 50 jpg:- | convert jpg:- -quality 50 jpg:- | convert jpg:- -quality 50 jpg:- | convert jpg:- -quality 50 jpg:- | convert jpg:- -quality 50 jpg:- | convert jpg:- -quality 50 jpg:- | convert jpg:- -quality 50 jpg:- | convert jpg:- -quality 50 jpg:- | convert jpg:- -quality 50 jpg:- | convert jpg:- -quality 50 jpg:-

答案 3 :(得分:0)

另一种使用eval的方法:

# wrap command to be run into a shell function
# it should read from stdin and write to stdout
pipecmd(){ convert -quality 1 - jpg:-; }

# generate appropriate number of copies
cmds=()
for x in {1..3}; do cmds[$x]=pipecmd; done

# flatten list into a single string
cmdlist="${cmds[@]}"

# insert the pipes
pipeline="${cmdlist// /|}"

# create a shell function that uses this pipeline
# we need eval so $pipeline is expanded properly
eval "runpipeline(){ $pipeline; }"

# or combine the previous two steps
eval "runpipeline2(){ ${cmdlist// /|}; }"

# check what we created
type pipecmd
type runpipeline
type runpipeline2

# do something
runpipeline <in.jpg >out.jpg

# this will hang!
runpipeline in.jpg >out.jpg

# or just use the wrapped command directly
pipecmd <in.jpg | pipecmd | pipecmd >out.jpg

答案 4 :(得分:0)

如果您确实想捕获中间输出,则可以使用$()res1=$(cmd1) res2=$(cmd2 <<< "$res1" ) res3=$(cmd3 <<< "$res2" )

请注意,在内部bash确实为此使用了一个临时文件,但是至少您不必这样做,但是如果您的内容包含引号,则使用通常的修饰符:

{{1}}