【问题标题】:How can I replace byte sequences in my data using Sed?如何使用 Sed 替换数据中的字节序列?
【发布时间】:2016-06-17 06:40:57
【问题描述】:

我的 Makefile 中有这条规则,用 CRLFNUL替换 |||(三个管道字符;十六进制 7c 7c 7c) >(回车+换行+空;十六进制0d 0a 00):

rom.hex: rom.txt  
    hexdump -C rom.txt | cut -c10-60 > rom.hex
    sed -i -e 's/  / /g' rom.hex
    sed -i -e 's/7c 7c 7c/0d 0a 00/g' rom.hex

这在某些时候有效 - 但是,如果 hexdump 的输出将 7c 7c 7c 序列分成两行,则它不会被 sed 匹配。

替换必须与匹配的长度相同,以免移动后续字节。

【问题讨论】:

  • 什么是the string "|||"?编辑您的问题以阐明您的要求并发布一些简洁、可测试的示例输入和预期输出,因为现在甚至不清楚问题是什么 - 修改您的 makefile 中的命令链,以便它们在单独的行中编写或编写要从 makefile 调用的脚本对跨行分割的文本执行某些操作。问题是您的 makefile 中的拆分行还是您的 makefile 正在操作的文件中的拆分行?
  • 假设您需要在输出中保留所有个输入字节,请使用hexdump -v,否则相邻相同行块中的重复项将打印为*,导致信息丢失。此命令说明了问题:hexdump -C <(printf '%*s' 64 ' ')

标签: string bash replace sed makefile


【解决方案1】:

您可以先进行替换,然后再转换为十六进制:

rom.hex: rom.txt
    sed -e 's/|||/\r\n\x00/g' $< | hexdump -v | cut -c'10-60' >$@

请注意,反斜杠转义是 GNU sed 扩展,因此这不是一个完全可移植的解决方案。如果您需要可移植的 sed 命令,则需要将其放在单独的文件中,因为您不能在命令行参数中包含 NUL。文字换行符也必须被引用:

s/|||/^M\
^@/g

为清楚起见,上面的控制字符是

73 2f 7c 7c 7c 2f 0d 5c  0a 00 2f 67      |s/|||/.\../g|

那么规则就是

rom.hex: rom.txt
    sed -f "transform.sed" $< | hexdump -v | cut -c'10-60' >$@

【讨论】:

  • ++ 一个巧妙的解决方案,额外简化了特殊的make变量$&lt;$@; OP 似乎也使用 GNU sed,所以我不认为这是一个问题,但应该注意的是你的 sed 命令需要它。
  • 看起来 3 位八进制转义符在 GNU sed 中不起作用,请改用 \x7c。同样,您必须在替换字符串中使用\x00 来生成实际的 NUL 字节。鉴于您可以简单地使用| 文字而不是\x7c,我们可以简化为:sed 's/|||/\r\n\x00/g'(仍然不可移植;sed 's/|||/'$'\r\\\n\x00'/g 几乎 工作(假设bashksh , zsh),但在合成完整的sed 脚本时,$'...' 生成的 NUL 字节被 shell 吃掉
  • 感谢有关可移植性的背景信息;遗憾的是,BSD sed 甚至在文件中也不喜欢 NUL:sed -f &lt;(printf 's/|||/\r\\\n\x00/g') &lt;&lt;&lt;'a|||b' 在 GNU sed 上运行良好,但在 BSD sedunterminated substitute in regular expression 上运行良好。我看到的唯一选择是使用 2-pass 解决方案:替换不同的字符。首先,然后使用tr将其翻译为NUL;例如sed 's/|||/'$'\r\\\n\x01'/g &lt;&lt;&lt;'a|||b' | tr '\1' '\0'
  • 我依靠 GNU sed --posix 来检查我的脚本——我自己不能保证。两遍解决方案可能存在ROM映像中没有“不可能发生”字符的问题... :-(
  • 好点关于“不可能发生”的角色 - 无赖。 sed POSIX spec 对替换字符串中的NUL 问题保持沉默,所以我猜这意味着它是由实现定义的。
【解决方案2】:

- Toby Speight's helpful answer 通过使用 GNU sed 在源代码处替换数据 巧妙地绕过了 OP 的问题,而无需对十六进制进行操作。表示(他的可移植替代方案不适用于 BSD sed,但这只是因为替换字符串中的 NUL 字符)。
- this 答案的价值在于解决 OP 的问题完全按照说明,尤其是使用 tr -s '\n' ' ',并提供相对简单的便携底部的解决方案 - 从字节表示/文本处理的角度来看很有趣。
- 请参阅my other answer,了解使用hexdump 的格式化选项直接生成所需输出格式的更简单解决方案。


注意

  • 以下解决方案将输入的字节值表示转换为行,以便能够稳健地使用sed 来替换值。
  • 如果您确实想要hexdump 默认生成的固定宽度多行输出,请将输出通过管道传输到... | fmt -w48

以下命令规范化hexdump -C 输出中的所有空格:

hexdump -vC rom.txt | cut -c10-60 | tr -s '\n' ' ' > rom.hex

注意添加-v,它可以防止信息丢失
如果没有-v,相邻重复行中的重复项将表示为*

结果是:

  • 单行由前导和尾随空格预订,

    • 如果要剥离这些,请参阅底部的便携式解决方案。
  • 字节值均由单个空格分隔;例如:
    23 21 2f 62 69 6e 2f 62 61 73 68 0a 0a 23 20 23 20 76 3d 24 5f 0a 23 20 23 20 65 63 68 6f 20 22 ....

  • 注意tr-s ("squeeze") 选项,执行了翻译之后(在这种情况下,\n,即),折叠运行目标字符(在本例中为(空格))多次出现在单字符运行中。

因此:

  • 不再需要用于规范行内部空格的中间 sed 命令 (sed -i -e 's/ /...)。

  • 最后的 sed 命令 (sed -i -e 's/7c 7c 7c/ ...) 可以安全地使用空格分隔的值作为搜索字符串,而不必担心在 hexdump -C 的输出中发生换行符的位置。

还有简化的空间:

  • 可以使用单个管道 - 无需以中间形式写入文件并稍后更新它。

    • 作为副作用,因为不再需要-i,所以sed 命令变得可移植(符合POSIX);虽然这种形式可以在 Linux 和 BSD/OSX 平台上运行,但它仍然不完全符合 POSIX,因为hexdump 是一个非标准实用程序;有关严格符合 POSIX 的解决方案,请参阅底部。
  • 特殊的make变量$&lt;,(第一)前提条件(rom.hex)和$@,目标(rom.txt)都可以使用。

  • 如果只需要字节值,则不需要hexdump-C 选项;这允许简化 cut 命令,顺便说一下,从输出中去除 前导 空间(并且也使 tr-s 选项变得不必要):

    李>
rom.hex: rom.txt  
    hexdump -v $< | cut -sd' ' -f2- | tr '\n' ' ' | sed 's/7c 7c 7c/0d 0a 00/g' > $@
  • cut -sd' ' -f2-:
    • -s 表示不包含-d 指定的分隔符(分隔符)的行将被跳过,这将跳过hexdump 可能输出的尾随空行(除了字节偏移列之外为空)。
    • -d' ' 使用单个空格作为分隔符将输入拆分为多个字段。
    • -f2- 通过行尾 (-) 输出第二个字段,有效地剥离了第一个字段(hexdump 输出中的输入地址 offset 列)。

为了使命令完全可移植,可以使用 POSIX 实用程序 od 代替非标准的 hexdump 实用程序。
此外,额外的sed 命令用于从输出中去除前导和尾随空格

rom.hex: rom.txt  
    od -t x1 -A n -v $< | tr -s '\n' ' ' | sed 's/^ //; s/ $//' | sed 's/7c 7c 7c/0d 0a 00/g' > $@
  • od -t x1 -A n -v 输出十六进制。 (x) 字节 (1) 跨固定宽度的多行,类似于hexdump,除了-A n 将输入地址偏移列清空; -v 确保所有字节都被表示;没有它,相邻的重复行将表示为*
  • 如上所述,tr -s '\n' ' ' 对空白进行规范化,以生成一条长行,其中字节值由一个空格分隔,并以一个前导和尾随空格结尾。
  • sed 's/^ //; s/ $//' 删除前导和尾随空格。
  • 命令的其余部分和以前一样。

【讨论】:

  • 使用tr -s '\n' ' 'tr -d '\n' 相比有什么优势?在这种情况下它们不是等效的还是我错过了什么?
  • @Carpetsmoker:它们等效:-s 确保,\n 转换为 ` ` 之后,多个空间的运行被标准化为每个空间。例如,tr -s '\n' ' ' &lt;&lt;&lt;$'a\n \n b' 产生 a&lt;space&gt;b&lt;space&gt;
【解决方案3】:

- 请参阅my other answer 了解如何解决问题如上所述,或者如果您需要POSIX 兼容 解决方案。
- 从字节表示格式的角度来看,这个答案很有趣。


注意

  • 以下解决方案将输入的字节值表示转换为行,以便能够稳健地使用sed 来替换值。
  • 如果您确实想要hexdump 默认生成的固定宽度多行输出,请将输出通过管道传输到... | fmt -w48

通过将格式选项传递给hexdump,可以绕过问题

hexdump -ve '1/1 "%02x "'

将所需的输出格式直接生成为单个行(将有一个尾随空格)。

  • -v 防止将重复字节缩写为 *
  • -e '1/1 "%02x "':
    • 1/1 指定将以下格式字符串应用于 1 个字节大小为 1 的单位,即每个字节。
    • "%02x " 是应用于每个字节的格式字符串:一个 2 位十六进制数字后跟一个空格。

总而言之,使用特殊的make 变量$&lt;,(第一个)先决条件(rom.hex)和$@,目标(rom.txt):

rom.hex: rom.txt  
    hexdump -ve '1/1 "%02x "' $< | sed 's/7c 7c 7c/0d 0a 00/g' > $@

替代解决方案,使用(也是非标准的)xxd 实用程序;像hexdump,但是,它在 Linux 和 BSD/OSX 上都可用:

rom.hex: rom.txt  
    xxd -p $< | tr -d '\n' | sed 's/../& /g; s/ $//' | sed 's/7c 7c 7c/0d 0a 00/g' > $@
  • xxd -p 打印字节值流没有分隔符,分成固定长度的行。

  • tr -d '\n' 从输出中删除换行符。

  • sed 's/../&amp; /g; s/ $//' 每 2 个字符后插入一个空格,然后删除行尾的尾随空格。


最后,正如Toby Speight 在 [since clean-up] 评论中指出的那样,您可以将 GNU 版本的od 与非标准的@987654346 一起使用@选项:

rom.hex: rom.txt  
    od -t x1 -A n -w1 -v $< | tr -d '\n' | sed 's/7c 7c 7c/0d 0a 00/g' > $@
  • od -t x1 -A n -w1 -v 输出十六进制。 (x) 字节 (1) 一次 1 个字节 (-w1); -A n 省略输入地址偏移列; -v 确保所有字节都被表示;没有它,相邻的重复行将表示为*
  • tr -d '\n' 只是删除所有换行符,由于每行都以空格开头,因此结果是一个带有前导空格的长行。

【讨论】:

    猜你喜欢
    • 2012-09-21
    • 1970-01-01
    • 1970-01-01
    • 2010-10-17
    • 2016-09-10
    • 1970-01-01
    • 1970-01-01
    • 2012-02-13
    • 2013-09-21
    相关资源
    最近更新 更多