Bash 读取忽略前导空格

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29689172/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-18 12:49:11  来源:igfitidea点击:

Bash read ignores leading spaces

bash

提问by Lahiru Chandima

I have file a.txtwith following content

我有a.txt以下内容的文件

    aaa
    bbb

When I execute following script:

当我执行以下脚本时:

while read line
do
    echo $line
done < a.txt > b.txt

generated b.txtcontains following

生成b.txt包含以下内容

aaa
bbb

It is seen that the leading spaces of lines have got removed. How can I preserve leading spaces?

可以看出,行的前导空格已被删除。如何保留前导空格?

回答by Etan Reisner

This is covered in the Bash FAQ entry on reading data line-by-line.

关于逐行读取数据的 Bash FAQ 条目中介绍了这一点。

The read command modifies each line read; by default it removes all leading and trailing whitespace characters (spaces and tabs, or any whitespace characters present in IFS). If that is not desired, the IFS variable has to be cleared:

# Exact lines, no trimming
while IFS= read -r line; do
  printf '%s\n' "$line"
done < "$file"

读取命令修改读取的每一行;默认情况下,它会删除所有前导和尾随空白字符(空格和制表符,或 IFS 中存在的任何空白字符)。如果不需要,则必须清除 IFS 变量:

# Exact lines, no trimming
while IFS= read -r line; do
  printf '%s\n' "$line"
done < "$file"

As Charles Duffy correctly points out (and I'd missed by focusing on the IFSissue); if you want to see the spaces in your output you also need to quote the variable when you use it or the shell will, once again, drop the whitespace.

正如查尔斯·达菲 (Charles Duffy) 正确指出的那样(我因关注这个IFS问题而错过了);如果您想查看输出中的空格,您还需要在使用变量时引用它,否则 shell 将再次删除空格。

Notes about some of the other differences in that quoted snippet as compared to your original code.

注意与原始代码相比,引用的代码段中的一些其他差异。

The use of the -rargument to readis covered in a single sentence at the top of the previously linked page.

-r参数 to的使用read包含在先前链接页面顶部的单个句子中。

The -r option to read prevents backslash interpretation (usually used as a backslash newline pair, to continue over multiple lines). Without this option, any backslashes in the input will be discarded. You should almost always use the -r option with read.

read 的 -r 选项可防止反斜杠解释(通常用作反斜杠换行符对,以在多行上继续)。如果没有这个选项,输入中的任何反斜杠都将被丢弃。您几乎应该总是将 -r 选项与 read 一起使用。

As to using printfinstead of echothere the behavior of echois, somewhat unfortunately, not portably consistent across all environments and the differences can be awkward to deal with. printfon the other hand is consistent and can be used entirely robustly.

至于使用printf而不是echo那里的行为echo,有点不幸的是,在所有环境中都不是可移植的,并且差异可能难以处理。printf另一方面是一致的,可以完全稳健地使用。

回答by Charles Duffy

There are several problems here:

这里有几个问题:

  • Unless IFSis cleared, readstrips leading and trailing whitespace.
  • echo $linestring-splits and glob-expands the contents of $line, breaking it up into individual words, and passing those words as individual arguments to the echocommand. Thus, even with IFS cleared at readtime, echo $linewould still discard leading and trailing whitespace, and change runs of whitespace between words into a single space character each. Additionally, a line containing only the character *would be expanded to contain a list of filenames.
  • echo "$line"is a significant improvement, but still won't correctly handle values such as -n, which it treats as an echo argument itself. printf '%s\n' "$line"would fix this fully.
  • readwithout -rtreats backslashes as continuation characters rather than literal content, such that they won't be included in the values produced unless doubled-up to escape themselves.
  • 除非IFS被清除,否则read去除前导和尾随空格。
  • echo $linestring-splits 和 glob-expands 的内容$line,将其分解为单独的单词,并将这些单词作为单独的参数传递给echo命令。因此,即使 IFS 被read及时清除,echo $line仍然会丢弃前导和尾随空格,并将单词之间的空格更改为每个空格字符。此外,仅包含字符的*行将被扩展为包含文件名列表。
  • echo "$line"是一项重大改进,但仍无法正确处理诸如 之类的值-n,它会将其视为 echo 参数本身。printf '%s\n' "$line"将完全解决这个问题。
  • readwithout-r将反斜杠视为连续字符而不是文字内容,这样它们就不会包含在生成的值中,除非加倍以逃避自身。

Thus:

因此:

while IFS= read -r line; do
  printf '%s\n' "$line"
done