bash - 在 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
解决方案
背景故事:怎么了
命令替换的输出($(...)
封装for
循环的语法)不会被 shell 解析为代码;相反,它只经过字符串拆分(在空格上拆分,除非IFS
已更改)和全局扩展(用*.txt
匹配列表替换字符串),然后直接放在命令行上sox
。
但是,<(...)
不是sox
;的指令 这是一个进程替换,指示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
-safe 命令
棘手的是,必须非常小心地防止数据(如文件名)被用于 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:收集文件描述符数组
这里有一些棘手的警告:实例在其 stdout 描述符的所有副本gsutil
都关闭之前不会退出,这意味着它们在完成后仍将运行,直到 shell 关闭它自己的副本。sox
#!/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:使用递归
推荐阅读
- backup - Why does wbadmin fail after a few days and report "Not enough storage is available to complete this operation"?
- shell - 如何将系统日期与日志文件日期匹配
- veracode - 为什么 veracode 认为 $(document) 是一个缺陷?
- javascript - Firefox 中的奇怪属性行为
- python - How to scrape the items loaded via a "view more" button using Scrapy
- python - 为辅助函数分配守护线程时发生分段错误
- spring-integration - 春季重试 IntegrationFlow
- java - 从不同的数据库和更新源获取更新
- java - 返回错误“测试类应该只有一个公共零参数构造函数”
- sed - sed 命令交换一行中的元素