在 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已更改(和 glob 扩展(用匹配列表替换*.txt等字符串(在直接放在命令行上以sox

。然而,<(...)不是sox指令;它是一个进程替换,指示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标准的shell——数组的使用不是严格强制性的——这意味着它也适用于根本不支持<(...)语法的shell。


方法 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 描述符的所有副本关闭之前不会退出,这意味着它们在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:使用递归

。如每个数组条目的进程替换中所述,不带评估

从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

最新更新