【问题标题】:how can I use a shell script to replace the name of a file with that file's contents如何使用 shell 脚本将文件名替换为该文件的内容
【发布时间】:2020-08-03 13:41:15
【问题描述】:

假设我有一个文件,其中包含任意数量的其他文件名(在可识别的分隔符中),例如

original-file-contents-which-should-remain
{{filename.txt}}
more-untouchable-contents
{{dir/myfile.md}}

如何使用 shell 脚本将文件名(和分隔符),即 {{filename.txt}}{{dir/myfile.md}} 替换为相应文件的内容?

我尝试过使用 sed,虽然如果我对目标文件名进行硬编码,它可以工作,但它无法从正则表达式中捕获文件名——即,以下内容删除了 {{myfile}},但没有输入 @987654325 的内容@(我猜可能正则表达式捕获仅适用于 s/old/new 命令):

sed -e "/{{\(.*\)}}/ { r \1" -e "d}" somefile

【问题讨论】:

  • 不,sed 无法做到这一点。您必须自己编写这样的工具。我相信它在 GNU awk 中是可行的。
  • 您包含的文件是否也可以包含此类语句,以便文件名对其内容的扩展需要递归完成?这些占位符字符串是否可以出现在中线,因此包含的文件内容也必须在中线开始/停止?
  • @KamilCuk sed 不是必需的,只是我已经尝试过的。 @EdMorton 递归不是必需的;占位符字符串可以缩进,但替换应保留其原始空格(即,不保留占位符的缩进)
  • @dave 我已经更新了占位符缩进的答案(即使我猜你已经自己调整过了)。

标签: string bash shell awk sed


【解决方案1】:

如果你有 GNU sed,那么你可以像这样使用e flag for the s command

sed -f - input_file <<'EOS'
    s/^\s*{{\([^']*\)}}\s*$/cat '\1'/e
EOS

首先将包含文件名的模式替换为 shell 命令cat filename,然后运行命令并将其输出通过管道传送回模式空间。

如您所见,sed 程序是从文本 here-doc 加载的,而不是通常的单引号字符串,因为它允许使用单引号将文件名括在 shell 命令中,同时保持整个 sed 命令干净(除非您完全信任输入文件的内容,否则单引号比双引号更安全)。再次为了安全起见,地址正则表达式防止文件名中出现单引号,并使用开始/行尾锚点来确保 shell 命令不会被可能的前导/尾随部分污染。

正如 KamilCuk 所指出的,您也可以使用 GNU awk。它带有一个附加函数库,例如readfile,这里很好地避免了shell命令。

gawk -i readfile '
    ! match($0, /^\s*\{\{(.*)}}\s*$/, refs) { print ; next }
    { printf "%s", readfile(refs[1]) }
' input_file

(由于portable answer from Ed Morton,更新了模式周围可能的空间和不需要的转义)

【讨论】:

  • 非常好。将2&gt;\/dev\/null 添加到cat 命令也可以处理不存在的文件,如果需要这样的处理。
  • @mathguy e 命令很有趣,但我更喜欢它的输入受到严格控制^^ 当它可能被像这里这样的东西喂食时,它有点棘手......
  • 也许是sed 's/^{{\(.*\)}}$/cat \1/e' file?
  • @potong 好吧,它确实适用于输入示例,但如果文件名包含特殊字符(有意或无意),如空格、$、转义序列......充其量它会只是无法导入文件,最坏的情况是它可能会运行有害的 shell 命令。您可能不想将此 sed 命令应用于像 {{$(rm -rf myprecious)}} 这样的模式,对吗? ^^
【解决方案2】:

这将与给定输入的每个 UNIX 框上的任何 shell 中的任何 awk 一起使用,例如您的示例:

awk '
match($0,/\{\{.*}}/) {
    fname = substr($0,RSTART+2,RLENGTH-4)
    while ( (getline < fname) > 0 ) {
        print
    }
    close(fname)
    next
}
{ print }
' file

例如

$ cat tam
When chapman billies leave the street,
And drouthy neebors neebors meet,
As market-days are wearing late,
{{foo}}
We think na on the lang Scots miles,
The mosses, waters, slaps, and stiles,
{{bar}}
Gathering her brows like gathering storm,
Nursing her wrath to keep it warm.

$ cat foo
And folk begin to tak the gate;
While we sit bousin, at the nappy,
And gettin fou and unco happy,

$ cat bar
That lie between us and our hame,
Whare sits our sulky, sullen dame,

.

awk '
match($0,/\{\{.*}}/) {
    fname = substr($0,RSTART+2,RLENGTH-4)
    while ( (getline < fname) > 0 ) {
        print
    }
    close(fname)
    next
}
{ print }
' tam
When chapman billies leave the street,
And drouthy neebors neebors meet,
As market-days are wearing late,
And folk begin to tak the gate;
While we sit bousin, at the nappy,
And gettin fou and unco happy,
We think na on the lang Scots miles,
The mosses, waters, slaps, and stiles,
That lie between us and our hame,
Whare sits our sulky, sullen dame,
Gathering her brows like gathering storm,
Nursing her wrath to keep it warm.

示例输入由Rabbie Burns提供。

【讨论】:

  • 我喜欢这个解决方案,因为它是可移植的,但我很难让它原谅分隔符前面的缩进空格
  • 这可以通过使用 match() 轻松处理,但您的示例中没有任何前导/尾随空格,因此(以及可能存在的各种其他内容)我不知道它有要处理,您的示例也没有显示文件中的替换文本是否应该缩进以匹配该占位符。当您发布示例以确保它涵盖您的所有用例时,这一点非常重要。无论如何,我更新了我的答案以做我现在认为你想要的。
  • 不错的便携式anwser。我的match() 调用确实没有必要,因为模式锚定在两侧没有空格。检查POSIX ERE spec,我看到右大括号确实不是特殊字符,很高兴知道。然而,在我阅读第 9.4.3 节时,这里未转义的左大括号似乎有一个未指定的结果,而不是只是不特别,或者我错过了什么?你怎么看?
  • @luciole75w 好点,} 在 RE 区间上下文中使用时是一个 RE 区间终​​止字符(即前面有 { 时),{ 是一个 RE 区间开始char 除非它是正则表达式中的第一个字符(在这种情况下,它的含义是未定义的)所以 {{ 中的第二个 { 可以被 awk 解释为那个元字符,而第一个的含义是未定义的。我现在通过转义两个{s 来解决这个问题,谢谢。
  • @EdMorton 感谢更新和随后的讨论。是的,我想我的例子可能会更好。在看到您的解决方案和评论之前,我认为分隔符之外的空格不会成为问题。
【解决方案3】:

使用vimex 版本:

ex -c 'g/{{/s/{{\([^}]*\)}}/\=readfile(submatch(1))/g' -c 'x!' input.txt

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-11-09
    • 2019-12-20
    • 2018-10-19
    • 2020-10-31
    • 2017-06-24
    • 2020-02-14
    • 2022-10-15
    • 1970-01-01
    相关资源
    最近更新 更多