【问题标题】:Sed is not writing to fileSed 没有写入文件
【发布时间】:2016-06-10 04:03:17
【问题描述】:

我只想更改 CSV 上的分隔符。 该文件来自外部服务器,因此分隔符是这样的:^A.

name^Atype^Avalue^A
john^Ab^A500
mary^Ac^A400
jack^Ad^A200

我想得到这个:

name,type,value
john,b,500
mary,c,400
jack,d,200

我需要将其更改为逗号(,)或制表符(,),但我的 sed 命令尽管输出正确,但不写入文件。

cat -v CSVFILE | sed -i "s/\^A/,/g"

当我使用上面的行时,它会正确输出由逗号而不是 ^A 分隔的文件,但它不会写入文件。

我也试过这样:

sed -i "s/\^A/,/g" CSVFILE

也不行... 我做错了什么?

【问题讨论】:

  • 第一个命令无法运行。您让 sed 对管道数据进行操作,没有 sed 可以就地查看和重写的文件。您的第二个命令似乎没问题。有错误信息吗?
  • 请提供文件中的一些示例行。特别是 cat -v 做了一些转换,可能会创建第二个示例中不存在的 ^A
  • 我相信^A 不在文件中。有一些不可打印的字符, cat -v 显示为^A。所以你可以这样做:`cat -v CSVFILE | sed "s/\^A/,/g" > CSVFILE.rpl`` 然后重命名 CSVFILE.rpl。
  • 我相信你的 ^A 是字节表示为 1 的字符,使用 `sed -i 's/\x01/,/g" CSVFILE'
  • 在模式中插入 ^A 字符:Ctrl + V + A

标签: linux bash awk sed cat


【解决方案1】:
  • 文字 ^A(两个字符,^A)是cat -v 可视化控制字符0x1(ASCII 码 1,命名为 SOH(标题开头))。 ^Acaret notation 的一个示例,用于表示不可打印的 ASCII 字符:

    • ^A 代表键盘组合 Control-A,当其前面带有通用转义序列 Control-V 时,您可以创建 Control-V em>actual 终端中的控制字符;换句话说,
      Control-VControl-A 将插入一个实际的0x1 字符。

    • 顺便提一下,插入符号(^<letter>)的逻辑是:字母对应所表示的控制字符的ASCII值;例如,A 对应于0x1D 对应于0x4^DEOT)。
      换一种说法:您将0x40 添加到控制字符的ASCII 值,以获取其以插入符号表示的字母表示的ASCII 值。
      ^@ 表示NUL0x0 字符)和@ 987654346@ 表示DEL (0x7f) 与此表示法一致,因为@ 具有ASCII 值0x40(即在ASCII 表中它位于A (0x41) 之前)和@ 987654353@ 被限制为 7 位(与最大 ASCII 值 0x7f 进行位与运算)产生 0x3f,这是 ? 的 ASCII 值。

    • 检查给定文件的 ASCII 值 外来控制字符,您可以将其通过管道传送到od -c,将0x1 表示为(八进制)001

  • 这意味着,将文件传递给sed直接,您不能使用插入符号,而必须使用实际控制字符 在您的s 电话中。

    • 请注意,当您使用 Control-VControl-A 创建 实际 0x1 字符时,它也会 以插入符号出现 - 如^A - 但在这种情况下,它只是终端对真正控制字符的可视化;虽然它可能看起来像两个可打印字符 ^A,但它不是。纯粹从视觉上你无法分辨出区别 - 这就是为什么使用转义序列或 ANSI C 引用的字符串来表示控制字符是更好的选择 - 见下文。
  • 假设您的 shell 是 bashkshzsh,则使用 Control-VControl-A 的更好选择是 使用ANSI C-quoted string 生成0x1 字符:$'\1'

    • 但是,正如 Lars Fischer 在对该问题的评论中指出的那样,GNU sed 也可以识别 0x1 的转义序列 \x01

因此,您的命令应该是:

sed -i 's/\x01/,/g' CSVFILE    # \x01 only recognized by GNU sed

或者,使用 ANSI C 引用的字符串:

sed -i $'s/\1/,/g' CSVFILE  

注意:虽然这种形式原则上可以与 BSD/OSX sed 一起使用,-i 的语法略有不同:你会必须使用sed -i '' $'s/\1/,/g' CSVFILE


为您的任务使用sed 的唯一原因是利用就地更新(-i);否则,tr 是更好的选择 - 请参阅 Ed Morton's answer

【讨论】:

  • '直接将文件传递给 sed 时,不能使用插入符号':不完全是,这取决于编辑器。在 Vim 中,键盘组合 CTRL-V CTRL-A 创建一个被 sed 正确匹配的字符(实际显示的控制字符 ^A)。
  • @Kenavoz:您所描述的不是插入符号本身,而是终端对也恰好使用插入符号的控制字符的可视化;我已经更新了我的答案以澄清。
  • 感谢您的解释。我会记住 \x01$'\1' 是更好的便携性的替代方案。
【解决方案2】:

这是tr 的工作目的:

tr '<control-A>' ',' < file > tmp && mv tmp file

显然用文字 control-A 替换 &lt;control-A&gt;

【讨论】:

  • 干得好;在 Bash、Ksh 和 Zsh 中,您可以使用 $'\1' 生成 Control-A。
  • 很高兴知道,谢谢。我通常会做 control-V control-a 来获得一个字面意思。
  • 想一想:tr '\1' ',' 应该也可以。
【解决方案3】:

如果你的sed 支持 -i 选项,你可以这样使用它:

sed -i.bak -e "s/\^A/,/g" CSVFILE

(假设源文件中的分隔符由两个字符 ^ 和 A 组成;如果 ^A 应该是指 Control-A,那么您将不得不相应地进行调整,例如使用 's/\x01/,/g'。)

否则,假设您想保留原始文件的副本(例如,如果结果不是您所期望的 - 见下文),可以使用如下的咒语:

mv CSVFILE CSVFILE.bak  &&  sed "s/\^A/,/g" CSVFILE.bak > CSVFILE

正如在别处指出的那样,如果源文件分隔符是 Control-A,您还可以使用 tr '\001' ,(或 tr '\001' '\t' 作为选项卡)。

需要注意的是,源文件中的分隔符可能会被精确地使用,因为逗号可能出现在分隔符分隔的“值”中。如果这是可能的,那么将需要一种不同的方法。 (参见例如https://www.rfc-editor.org/rfc/rfc4180

【讨论】:

    【解决方案4】:

    如果它在 OS X 下运行:

    • -i添加扩展名以写入新文件:

      sed -i.bak "s/^A/,/g" CSVFILE
      
    • 或者原地写:

      sed -i '' "s/^A/,/g" CSVFILE
      
    • 您也可以在您的 sed 上使用 cat 但没有 -i 输出到文件 命令:

      cat -v CSVFILE | sed "s/^A/,/g" > ouput
      

    确保你这样写^A:

    Ctrl+V+Ctrl+A

    【讨论】:

    • 虽然-i 需要 在 OSX 上的选项参数是正确的,但问题标记为 linux,而在 Linux 上是 -i '',不会不行。
    • OS X 是基于 Unix 的,对于这个问题,选择 linux 可能比 mac os x 标签更相关。一个简单的sed -i 's/\^A/,/g' 不起作用的原因更像是它是在 OS X 上运行的。但我可能错了......
    • 如果这是您的猜测,我建议您在答案中明确说明。但是,我认为 tat 更有可能是正确的,并且问题源于从字面上理解控制字符 0x1^A 可视化。
    • 我更新了我的答案。但我们注意到 ^A 确实是问题所在(“它正确输出了由逗号而不是 ^A 分隔的文件”)。
    • 感谢更新;回复^A:这只是因为cat -v首先应用于文件。
    猜你喜欢
    • 2015-06-18
    • 2016-07-13
    • 1970-01-01
    • 2013-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多