如何在不删除尾随换行符的情况下将stdin捕获为变量?


9

在shell脚本中...

如何在不删除尾随换行符的情况下将stdin捕获为变量?

现在我已经尝试:

var=`cat`
var=`tee`
var=$(tee)

在所有情况下$var,输入流都不会包含尾随换行符。谢谢。

还:如果输入中没有尾随换行符,则解决方案不得添加

根据接受的答案进行更新:

我在代码中使用的最终解决方案如下:

function filter() {
    #do lots of sed operations
    #see https://github.com/gistya/expandr for full code
}

GIT_INPUT=`cat; echo x`
FILTERED_OUTPUT=$(printf '%s' "$GIT_INPUT" | filter)
FILTERED_OUTPUT=${FILTERED_OUTPUT%x}
printf '%s' "$FILTERED_OUTPUT"

如果您想查看完整代码,请参阅github页面的expandr,这是我为信息安全目的而开发的一个开源git关键字-扩展过滤器外壳脚本。根据.gitattributes文件(可以是特定于分支的文件)和git config中设置的规则,无论何时将其签入或签出存储库,git都会通过expandr.sh Shell脚本通过管道传输每个文件。(这就是为什么保留任何尾随的换行或缺少换行的关键。)这使您可以清除敏感信息,并交换不同的特定于环境的值集以进行测试,暂存和实时分支。


您不必在这里做什么。filter需要stdin-它运行sed。你抓住stdin$GIT_INPUT,然后打印回stdout了一个管道,filter并赶上它stdout$FILTERED_OUTPUT,然后打印回stdout。上面示例底部的所有4行都可以替换为:filter。这里没有冒犯的意思,只是...您在努力工作。您通常不需要外壳变量,只需将输入定向到正确的位置并将其传递即可。
mikeserv

不,我在这里所做的事情是必要的,因为如果我只是这样做filter,那么它将在所有最初未以换行符结尾的输入流的末尾添加换行符。实际上,我最初只是这样做,filter但是遇到了导致该解决方案的问题,因为“总是添加换行符”和“总是删除换行符”都不是可接受的解决方案。
CommaToast

sed可能会执行额外的换行符-但是您应该filter不要用其他所有的换行符来处理它。而且您拥有的所有这些功能基本上都做相同的事情-a sed s///。您正在使用外壳将保存在其内存中的数据通过管道传输到,sed以便sed可以用外壳存储在其内存中的其他数据替换该数据,以便sed可以将其通过管道传输回外壳。为什么不只是[ "$var" = "$condition" ] && var=new_value呢?我也没有得到数组-您是否在存储数组名称,[0]然后用sed替换其中的值[1]?也许聊天?
mikeserv

@mikeserv-将代码移入内部的好处是filter什么?它按原样完美运行。关于链接上的代码如何工作以及为什么要像以前那样设置,是的,让我们在聊天室中讨论一下。
CommaToast 2014年

Answers:


7

在将值存储在变量中之前,将删除尾随的换行符。您可能想要执行以下操作:

var=`cat; echo x`

并使用${var%x}代替$var。例如:

printf "%s" "${var%x}"

请注意,这解决了尾随换行符的问题,但解决了空字节1(如果标准输入不是文本)的问题,因为根据POSIX命令替换

如果输出包含任何空字节,则行为未指定。

但是shell实现可以保留空字节。


文本文件通常会包含空字节吗?我不明白他们为什么会这样。但是您刚才提到的脚本似乎无效。
CommaToast 2014年

@CommaToast文本文件不包含空字节。但是问题只是说标准输入/输入流,在最一般的情况下可能不是文本。
vinc17 2014年

好。好吧,我从命令行尝试了它,但是它什么也没做,在我的脚本本身中,您的建议失败了,因为它在文件末尾添加了“ ...”。另外,如果那里没有换行符,那么它仍会添加一个。
CommaToast 2014年

@CommaToast“ ...”只是一个例子。我已经澄清了答案。没有添加换行符(请参见示例中“ ...” 之前的文本)。
vinc17

1
好吧,贝壳不应该藏东西,那不是很酷。这些炮弹应该被发射。当我的计算机认为它的知识比我更好时,我不喜欢它。
CommaToast 2014年

4

您可以使用read内置功能来完成此操作:

$ IFS='' read -d '' -r foo < <(echo bar)

$ echo "<$foo>"
<bar
>

对于要读取STDIN的脚本,它只是:

IFS='' read -d '' -r foo

 

我不确定这将在什么壳中工作。但是在bash和zsh中都可以正常工作。


既不-d也不是过程取代(<(...))是便携式的; 例如,此代码将不适dash用于。
chepner 2014年

好吧,流程替代并不是答案的一部分,而仅仅是示例中证明其有效的一部分。至于-d,这就是为什么我将免责声明放在底部。OP未指定外壳。
帕特里克

@chepner-尽管样式略有不同,但该概念确实适用于dash。您只需要<<HEREDOC\n$(gen input)\nHEREDOC\ndash-in-中将管道用于heredocs,就像其他shell将管道用于进程替换一样,这没有区别。该read -d事情只是指定一个分隔符-你可以做同样的一打的方式-只要确定它。虽然您将需要一些尾巴gen input
mikeserv

您设置了IFS ='',这样它就不会在它读取的行之间插入空格吗?很酷的把戏。
CommaToast 2014年

实际上,在这种情况下IFS=''可能没有必要。这意味着read不会折叠空间。但是,当它读入单个变量时,它没有任何作用(我记得)。但是让我感到更安全:-)
帕特里克

2

您可以像这样:

input | { var=$(sed '$s/$/./'); var=${var%.}; }

$var只要您走出该{ current shell ; }组,您所做的任何事情都会消失。但是它也可以像这样工作:

var=$(input | sed '$s/$/./'); var=${var%.}

1
应当注意,对于第一解决方案,即必须$var{ ... }分组中使用,并非总是可能的。例如,如果此命令在循环内运行,而又需要$var在循环外运行。
vinc17 2014年

@ vinc17 -如果它是一个循环我需要使用的话,我会用它来代替的{}括号。它是真实的- 并且在答案明确指出 -这对于价值$var非常有可能完全消失时的{ current shell; }分组是关闭。还有什么比“ 无论做什么都消失了……” 明确的说法呢$var
mikeserv

@ vinc17-也许是最好的方法:input | sed "s/'"'/&"&"&/g;s/.*/process2 '"'-> &'/" | sh
mikeserv

1
还有的_variables功能bash_completion,它将命令替换的结果存储在全局变量中COMPREPLY。如果使用管道解决方案保留换行符,则结果将丢失。在您的回答中,给人的印象是两种解决方案都一样好。此外,应该注意的是,管道解决方案的行为在很大程度上取决于外壳程序:用户可以echo foo | { var=$(sed '$s/$/./'); var=${var%.}; } ; echo $var使用ksh93和zsh进行测试,并认为这是可以的,而此代码是有问题的。
vinc17 2014年

1
您没有说“它不起作用”。您刚刚说过“ $var消失”(实际上不正确,因为这取决于外壳,该行为未由POSIX指定),这是一个中性的句子。第二种解决方案更好,因为它不受此问题的困扰,并且其行为在所有POSIX shell中都是一致的。
vinc17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.