【问题标题】:Building filename from command outputs results in ambigous redirect error从命令输出构建文件名会导致不明确的重定向错误
【发布时间】:2021-03-23 17:42:52
【问题描述】:

我想编辑一个文件并将其写入最后没有 .template 的另一个位置。为此,我连续使用两个命令,这应该给我新的文件名,但 bash 不接受它。我听说你应该用反引号来做这个,但我不能让它工作(第一个 sed 是正确的,不要打扰):

sed -E '/^\s*(#.*)?$/d; s/^\s*(\S+)\s+("([^"]*)"|(\S+)).*/s\/\1\/\3\4\/g/' "$file" > `$(basename $file) | sed -E 's/(.*).template$/.translated\1/g')`

我收到这样的错误:

translate: command substitution: line 25: syntax error near unexpected token `)'
translate: command substitution: line 25: `$(basename $file) | sed -E 's/(.*).template$/.translated\1/g')'
translate: line 25: `$(basename $file) | sed -E 's/(.*).template$/.translated\1/g')`: ambiguous redirect

重定向到文件名部分应该是什么样的?

【问题讨论】:

  • >"$(basename "$file")" 不那么模棱两可——注意引用。即使这样,如果file 为空,也会有歧义。
  • 但是,当您使用 both > and | 来重定向单个命令的标准输出时,您期望会发生什么?它只能去一个地方——一个文件一个管道——不能同时去两者。
  • file_base=${file##*/} 视为优先命令,然后使用类似于>"${file_base%.template}.translated" 的内容。即使不是更正确,使用内置 bash parameter expansion 也比启动 sed 的新副本仅处理单行输入要快几个数量级。
  • Re: “应该用反引号来做这个”——反引号从不优于$(...),这是自最初发布以来执行命令替换的标准化方式1991 年的 POSIX.2。
  • (如果你真的希望你的输出文件是一个名称以.translated开头的隐藏文件,那么你可能想要>".translated${file_base%.template}"

标签: bash command-substitution


【解决方案1】:

第 1 部分:为什么会发生

让我们看看为什么您会收到“模糊重定向”错误。

当你运行时:

foo > `$(basename $file) | sed -E 's/(.*).template$/.translated\1/g')`

...这是告诉 shell 首先运行一个命令:

$(basename $file) | sed -E 's/(.*).template$/.translated\1/g'

...并将其输出用作重定向目标。

但是,$(basename $file) | ...basename $file | ... 相同!假设您的文件名为/path/to/foo.template。在第一个命令替换发生后,它被翻译成:

foo.template | sed -E 's/(.*).template$/.translated\1/g'

...它正在运行一个名为 foo.template 的命令并将其输出提供给sed

一个名为foo.template的命令吗?如果你不这样做,那只会在 stderr 上产生一个错误,而在 stdout 上什么也没有。由于 stdout 是输入到 sed 的内容,这意味着 sed 根本没有收到任何输入,所以它写入根本没有输出,所以你有一个空文件名。

并且试图重定向到一个空的文件名...是模棱两可的!这是你的错误。


第 2 部分:正确行事

我假设您的两个 sed 命令都执行您希望它们执行的操作(这意味着您想要 foo.template 创建一个名为 .footranslated 的文件——如果您 不想创建这个隐藏文件,你的第二次sed操作很错误)。

file_base=${file##*/}
sed -E '/^\s*(#.*)?$/d; s/^\s*(\S+)\s+("([^"]*)"|(\S+)).*/s\/\1\/\3\4\/g/' "$file" \
  >".translated${file_base%.template}"

...或者,如果您的实际意图是将扩展名.template 替换为扩展名.translated,则改为:

file_base=${file##*/}
sed -E '/^\s*(#.*)?$/d; s/^\s*(\S+)\s+("([^"]*)"|(\S+)).*/s\/\1\/\3\4\/g/' "$file" \
  >"${file_base%.template}.translated"

【讨论】:

  • 感谢您的精心解答!我很难理解这条评论,我认为这是我误解的核心:>>然而,当你同时使用 > 和 | 时,你期望会发生什么重定向单个命令的标准输出?它只能去一个地方——一个文件或一个管道——不能同时去两者。
  • 我的意思是:foo > bar | baz 告诉foo 的输出转到bar 转到baz 的标准输入。但是,foo 所做的每个write() 只能去一个地方;它可以最终转到bar,或者它可以转到baz,但它不能同时做这两个(没有像tee 这样可以为每条数据做多个write()s read() s)。也就是说,最初的评论来自于我注意到你的反引号的确切位置之前(这是更喜欢$(...) 的另一个原因——它对读者来说更突出)。
  • 啊有趣,我会研究 tee!但是,我还需要做什么来评估第二个语句栏 | baz 到一个 > 可以作为变量名输入的字符串?这可能吗?
  • foo | tee bar | baz 将使foo 的输出同时转到名为bar 的文件和baz 的标准输入。但这与您在这里真正关心的情况不同。 afaict,你真正要找的是foo >"$(bar | baz)",这个比较简单。
猜你喜欢
  • 2022-10-25
  • 1970-01-01
  • 2013-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-24
  • 1970-01-01
相关资源
最近更新 更多