bash - 在 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 的指令;这是 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/

相关文章:

linux - 在 bash 脚本中包含相对路径中的二进制文件

java - Shell 脚本一个接一个地运行

bash - 管道 heredoc 的多行语法;这是可移植的吗?

linux - 在 linux 中通过 bash shell 脚本更新 phpMyAdmin blowfish_secret

python - 设置 Virtualenv 和 Virtualenvwrapper 的问题

bash - 查找包含给定文本的文件

linux - 如何编写一个返回是否发生错误的 shell 函数?

bash - 用于在 bash printf 中着色的 ANSI 转义码

与通配符匹配的正则表达式

python - 更改电子表格中每个单元格中首字母的大小写