在bash中使用循环将过程替换添加到命令行

时间:2018-08-14 20:43:59

标签: bash

在命令行中,我可以使用gsutil cat将输入通过管道传递到sox。完美运作。这是从命令行运行的代码:

sox <(gsutil -q cat gs://some-bucket/201808130800.wav) ./test-show.wav -t 
wav trim 0 10

但是,在我的bash脚本中,它失败了。我尝试了许多配置。一天结束时,sox抱怨文件名不正确。相同的确切代码从命令行运行。

这是代码行。所有变量都是正确的。如果我使用sox来处理本地文件,那么效果很好。

sox $(for ((i=0; i<file_count; i++)); do
file_time=$(date "+%s" --date="$(date -d @$base_time) +$i hours")
file_name=$(date --date="@$file_time" "+%Y%m%d%H%M")
echo -e "<(gsutil -q cat gs://somebucket/$file_name.wav)"
done) "$target/$show_name.wav -t wav trim $f_offset $run_time"

如果我在命令前添加回显,则屏幕上显示的内容恰好是从命令行执行的内容。我可以将输出复制并粘贴到命令行,并且可以正常工作。

以下是输出:

sox FAIL formats: can't open input file `gs://somebucket/201808130800.wav)': No such file or directory

任何帮助将不胜感激。

附录:

我几乎可以和它一起工作:

gsutil cat "$(for ((i=0; i<file_count; i++)); do
  file_time=$(date "+%s" --date="$(date -d @$base_time) +$i hours")
  file_name=$(date --date="@$file_time" "+%Y%m%d%H%M")
  echo "gs://somebucket/$file_name.wav"
done)" | sox -V4 - $target/$show_name.wav trim $f_offset $run_time

2 个答案:

答案 0 :(得分:3)

背景故事:出了什么问题

shell不会将命令替换(封装$(...)循环的for语法)的输出解析为代码;相反,它仅通过字符串分割(在空格上分割,除非IFS有所更改)和全局扩展(用匹配列表替换*.txt之类的字符串)后才直接放在命令行上到sox

但是,<(...)不是sox的指令; process substitution指示 shell 放置一个文件名,在调用sox之前,该文件名可从命令行读取该文件名以检索子进程的输出。

您可以通过以下几种方式动态生成与命令输出关联的文件名。


方法1:改用命名管道

这里要做的一件更容易的事情是建立显式(命名)FIFO,而不是依靠进程替换来创建匿名FIFO:

#!/usr/bin/env bash

tempdir=$(mktemp -d "${TMPDIR:-/tmp}/sox-pipes.XXXXXX") || exit

declare -a fifos=( )

cleanup() {
  rm -rf "$tempdir"
  kill "${!fifos[@]}"
}
trap cleanup EXIT

for ((i=0; i<file_count; i++)); do
  file_time=$(date "+%s" --date="$(date -d @"$base_time") +$i hours")
  file_name=$(date --date="@$file_time" "+%Y%m%d%H%M")
  mkfifo "$tempdir/$file_name.fifo"
  gsutil -q cat gs://some-bucket/$file_name.wav >"$tempdir/$file_name.fifo" &
  fifos[$!]="$tempdir/$file_name.fifo"
done

sox "${fifos[@]}" ./test-show.wav -t

可以对这种方法进行改造,使其可以在任何符合POSIX的外壳程序上使用-严格要求不使用数组-这意味着它也可以在完全不支持<(...)语法的外壳程序上使用。


方法2:生成安全可靠的eval命令

棘手的事情是,必须非常小心地进行操作,以防止将数据(如文件名)用于shell注入攻击。请注意,使用printf %q来转义被替换为字符串的数据。

#!/usr/bin/env bash

cmdline=''
for ((i=0; i<file_count; i++)); do
  file_time=$(date "+%s" --date="$(date -d @"$base_time") +$i hours")
  file_name=$(date --date="@$file_time" "+%Y%m%d%H%M")
  printf -v piece ' <(gsutil -q cat gs://some-bucket/%q.wav) ' "$file_name"
  cmdline+="$piece"
done

eval "sox ${cmdline} ./test-show.wav -t"

方法3:收集文件描述符数组

这里有一些棘手的警告:gsutil实例要等到它们的stdout描述符的所有副本关闭后才能退出,这意味着它们在{{1 }}完成,直到shell关闭自己的副本为止。

sox

方法4:使用递归

...如Process Substitution For Each Array Entry, Without Eval

中所述

答案 1 :(得分:0)

在OP中,先前已将其编辑为问题:

  

这是行之有效的最终解决方案。首先,我从wav源文件更改为原始PCM文件,以删除44字节的wav标头。这使餐饮变得无缝。这是一个班轮:

gsutil -q cat $(for ((i=0; i<file_count; i++)); do
  file_time=$(date "+%s" --date="$(date -d @$base_time) +$i hours")
  file_name=$(date --date="@$file_time" "+%Y%m%d%H%M")
  echo "gs://somebucket/$file_name.raw"
done) | sox -t raw -b 16 -c 2 -e signed-integer -r 48k -L - 
$target/$show_name.wav trim $f_offset $run_time