我有一个由第三方(Windows)提供的文件,它不受我们控制。
我观察到它有如下特殊字符:
root@DKERP:~# cat -ev ~/check_disk_space.sh
M-oM-;M-?#!/bin/bash^M$
^M$
# Arguments: <mount_points> <min_free_space>^M$
^M$
我尝试了下面的解决方案,它删除了所有特殊字符,但是M-oM-;M-?
sed -i -e 's/\r//g; s/'$'\302''//g; s/'$'\273''//g' ~/check_disk_space.sh
您能建议我如何删除所有特殊字符吗?
答案1
总结
跳至名为“通用解决方案sed
”的部分。
解释
M-oM-;M-?
是如何cat -ev
向你展示UTF-8 的 BOM。
看起来有问题的文件应该是 shell 脚本。Linux 中的脚本应采用 UTF-8 编码,不带 BOM,且行以\n
( LF
,$
在 的输出中cat -ev
) 结尾,而不是以\r\n
( CRLF
,^M$
在 的输出中cat -ev
) 结尾。您绝对应该删除这些\r
字符和 BOM。
dos2unix
是一个转换换行符的工具,它还可以删除 BOM。但它不是一个可移植的工具,您的 Linux 中可能有它,也可能没有。
麻烦的BOM是 (十六进制) EF BB BF
,以八进制表示则是357 273 277
。
您的解决方案sed
尝试删除273
;如果它有效,它应该删除M-;
。如果它有效,那么添加357
和277
将很简单。但是您使用的命令不符合 POSIX 标准,至少有两个原因:
命令中的语法
$'\273'
旨在由 shell 扩展,并sed
应获得逐字字节。Bash 支持该语法,但纯 POSIX shell 不支持。POSIX 没有
-i
指定sed
。即使支持的实现-i
在细节上也存在差异。
简单(但不完美)的解决方案tr
一个 POSIX 工具,用于删除字节(理论上是字符,但是并非总是如此) 是tr -d
。它支持八进制表示,因此您不需要 shell 进行扩展$'\273'
等。命令如下:
<source_file tr -d '\r\357\273\277' >destination_file
我不清楚你为什么要删除八进制表示的字节302
。\302
如果你认为需要的话,请将其添加到上面的命令中。
不幸的是,我们想要删除的字节中至少有一些可能出现在 BOM 之后。例如,字符在 UTF-8 中»
是(八进制) 。(也许您的设计就是为了删除这个确切的字符?)302 273
sed
另一种简单(但仍然有些缺陷)的解决方案是dd
为了避免删除非 BOM 的匹配字节,请考虑采用其他方法。您可以跳过前三个字节,无论它们是什么:
<source_file dd bs=1 skip=3 >destination_file
\r
您也想删除,因此完整命令将是:
<source_file dd bs=1 skip=3 | tr -d '\r' >destination_file
即使没有 BOM,我们的原始版本tr
也应该可以正常工作,但如果文件中有奇特的字符(如»
),则该工具可能会因删除过多而造成损害。另一方面,我们dd
只会删除前三个字节,因此它不会触及文件中后面的字节,但如果没有三字节 BOM,它会造成损害。
如果你确定 UTF-8 有 BOM,那么使用这个解决方案dd
就可以了。
通用解决方案sed
通用的解决方案如下:
<source_file sed "
1 s|^$(printf '\357\273\277')||;
s|$(printf '\r')||g;
" >destination_file
它消除了精确的仅从第一行开头开始 BOM(如果存在 BOM);并且它不会触及»
可能在后面出现的类似字符。它还会删除所有回车符。请注意,在 UTF-8 中,我们表示的字节\r
不能作为多字节字符的一部分出现,因此它不会出现与 类似的问题\273
。
如果您sed
支持,-i
则使用它(sed -i… "…" source_file
)您就完成了。答案继续,因为您请求了 POSIX 解决方案,即没有sed -i
。
做的工作sed -i
使用tr
或dd
+tr
或之后sed
,您可以
mv destination_file source_file
sed -i
会在后台执行此操作(或类似操作)。请注意,您的(以及之后的destination_file
新)拥有与旧无关的所有权和模式(权限),您可能需要更正它们。为避免此问题,请尝试使用而不是:source_file
mv
source_file
cat
mv
<destination_file cat >source_file && rm destination_file
写回cat
不是原子的(在某种意义上mv
在单个文件系统中很可能是原子的),但它不会改变你的 的所有权或模式source_file
。当然,你需要对 有写权限才能做到source_file
这一点;所以如果所有权和模式对你来说不合适(你无法写入文件)那么 就cat
行不通了,mv
也许。不管怎样,最终你可能(或可能不需要)chmod
和/或chown
。
请注意,我们的mv
或我们的重定向cat >…
会破坏源数据。只有在您确定正确后,才应运行一个异或另一个destination_file
。使用链接命令&&
可能会有所帮助。示例:
<source_file sed "
1 s|^$(printf '\357\273\277')||;
s|$(printf '\r')||g;
" >destination_file \
&& <destination_file cat >source_file \
&& rm destination_file