从命令行,我可以使用 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
最佳答案
背景故事:出了什么问题
命令替换的输出(封装 $(...)
循环的 for
语法)不会被 shell 解析为代码;相反,它只经过字符串拆分(在空格上拆分,除非 IFS
已更改)和全局扩展(用匹配列表替换像 *.txt
这样的字符串),然后直接在命令行上放入 sox
。 .
但是,<(...)
不是 sox
的指令;这是 process substitution指示 shell 在调用 sox
之前在命令行上的该位置放置一个可以读取的文件名,以检索子进程的输出.
您可以通过几种方式动态生成与命令输出关联的文件名,如下所述。
方法 1:使用命名管道
这里更容易做的事情之一是构建显式(命名)的 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 标准的 shell 上工作——数组的使用并不是严格强制的——这意味着它也可以在不支持 <(...)
的 shell 上工作。根本没有语法。
方法 2:生成 eval
-安全命令
棘手的是,必须非常小心地完成此操作,以防止数据(如文件名)被利用到 shell 注入(inject)攻击。注意 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
在其标准输出描述符的所有副本关闭之前,实例不会退出,这意味着它们仍将在 sox
之后运行直到 shell 关闭它自己的副本为止。
#!/usr/bin/env bash
case $BASH_VERSION in ''|[123].*|4.0.*) echo "ERROR: Bash 4.1 required" >&2; exit 1;; esac
gsutil_fds=( )
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")
exec {gsutil_fd}< <(gsutil -q cat gs://some-bucket/"$file_name".wav)
gsutil_fds+=( /dev/fd/"$gsutil_fd" )
done
sox "${gsutil_fds[@]}" ./test-show.wav -t
for fd in "${gsutil_fds[@]#/dev/fd/}"; do
exec {fd}>&- # close the fifo so this copy of gsutil can exit
done
方法 4:使用递归
...如 Process Substitution For Each Array Entry, Without Eval 中所述
关于bash - 在 bash 中使用循环将进程替换添加到命令行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51849506/