当我将数据附加到文件时,curl / wget 会添加额外的 ^M

当我将数据附加到文件时,curl / wget 会添加额外的 ^M

这件事让我很困惑。我试图将两个不同的主机文件下载到一个文件中,如果我单独执行此操作,则一切正常,但是当我将第一个文件附加到第二个文件时,^M主机文件的每一行都会出现一个奇怪的字符。

举一个真实的例子,我正在做什么

wget https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts -O /etc/hosts && curl -s "https://raw.githubusercontent.com/CHEF-KOCH/CKs-FilterList/master/HOSTS/CK's-Spotify-HOSTS-FilterList.txt" >> /etc/hosts

现在/etc/hosts有这些: 在此处输入图片描述

但当我单独做这件事时,

curl -s "https://raw.githubusercontent.com/CHEF-KOCH/CKs-FilterList/master/HOSTS/CK's-Spotify-HOSTS-FilterList.txt" > /tmp/hosts

现在/tmp/hosts完全正常

在此处输入图片描述

为什么会发生这种情况?为什么当我单独下载文件时,我不会得到错误的换行符,而当我将它们组合在一起时,我却得到了错误的换行符。它应该是 0x0a 而不是 0x0a0x0d,为什么会发生这种情况?

如果您需要查看正在下载的文件,您可以转到命令中的链接:

  1. https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
  2. https://raw.githubusercontent.com/CHEF-KOCH/CKs-FilterList/master/HOSTS/CK%27s-Spotify-HOSTS-FilterList.txt

编辑:我试图只附加第二主机文件复制到一个愚蠢的 hosts 文件中,也发生了同样的情况,所以我们可以忽略第一个文件是问题的原因

答案1

没有工具可以增加任何东西。这很令人困惑(但这不是你的错),原因有几个。

有两种常见的行尾:

  • Unix 风格,一个字符表示LF(或\n0x0a),
  • Windows 风格,两个字符,CRLF(或\r\n0x0d 0x0a)。

您从两个不同的 URL 下载。服务器似乎声称每个文件都是text/plain,因此他们应该使用CRLF。第二个(你curl)确实使用了CRLF,但第一个(你wget)却非法使用了 sole LF

如果你仅从第一个 URL 下载(无论是否带有wgetcurl)并将结果存储在hosts1文件中,则将file hosts1产生:

hosts1: UTF-8 Unicode text

(这意味着行尾是LF,否则就是UTF-8 Unicode text, with CRLF line terminators)。

如果仅从第二个 URL 下载并将结果存储在文件中hosts2,则将file hosts2产生:

hosts2: ASCII text, with CRLF line terminators

hosts12如果您按照上述方式将两者下载到同一个文件(比如说),那么您将获得LF来自第一个 URL 的行的行尾,以及CRLF来自第二个 URL 的行的行尾。

实际上,任何试图判断文件是否使用LFCRLF检查的工具最多只检查几行初始行,而不是全部。尝试一下file hosts12,你会得到:

hosts12: UTF-8 Unicode text

和 的情况完全一样hosts1。当你 时也会发生同样的情况vim hosts12:编辑器根据LF文件的开头检测行尾。然后你跳到结尾,你会看到许多^M表示字符的 -s CRvim打印它们,因为它不认为CR在这种情况下是正确行尾的一部分。

但是,当您 时vim hosts2,编辑器会正确地将行尾检测为。之前打印的CRLF相同字符现在对您隐藏了,因为将它们视为正确行尾的一部分。如果您手动添加新行,即使您使用的是 Unix,也会使用 Windows 样式的行尾。您可能认为该文件“完全正常”,但它不是正常的 Unix 文本文件。CR^Mvimvim

造成混淆的原因是服务器上的两个文件使用了不同的行尾;然后vim尝试变得聪明。

在 Linux(一般为 Unix)中,您希望/etc/hosts将其用作LF行尾。请参阅 POSIX 定义线换行符。明确指出该角色是\n

3.243 换行符(<newline>)输出流中的字符,表示打印应从下一行的开头开始。它是C 语言中
指定的字符。'\n'

我认为工具没有义务支持这一点。简单的解决方案是完全按照您的操作\r\n运行,然后调用。wget … && curl … >> …dos2unix /etc/hosts

如果我是你,我会和其他文件,比如说/etc/hosts.tmp。我会使用,,,,wget。只有当文件完整时,我才会用它来替换。curldos2unixchmod --reference=/etc/hostschown --reference=/etc/hostsmv/etc/hostsrename(2)相关:

如果newpath已经存在,它将被原子替换,以便另一个尝试访问的进程不会newpath发现它丢失。

因此,任何进程都会找到旧的/etc/hosts(之前mv)或新的(之后mv)。您当前的方法直接使用,/etc/hosts允许另一个进程发现文件不完整或文件末尾附近有错误的行结尾的情况。

相关内容