如何让 bash 不将 \n 分配给变量?

如何让 bash 不将 \n 分配给变量?

我有一个或多或少像这样的变量:

$ echo "$LIST"
file1: ok
file2: ok
file3:
file4:
file5: ok

然后我需要获取不正常的文件列表:

$ sed '/:\s.\+$/d' <<< "$LIST"
file3:
file4:

该文件有效,但如果列表中没有不正常的文件,则会发生这种情况:

$ echo "$LIST"
file1: ok
file2: ok
file3: ok
file4: ok
file5: ok
$ sed '/:\s.\+$/d' <<< "$LIST"
$ NEWLIST=$(sed '/:\s.\+$/d' <<< "$LIST")
$ echo "$NEWLIST"

$ cat -A <<< "$NEWLIST"
$
$ wc -l <<< "$NEWLIST"
1
$ wc -c <<< "$NEWLIST"
1

添加到变量中的这一换行符(我认为是 \n)使我的程序变得一团糟,因为它标识为列出了一个文件,因为我用来wc -l知道那里存在多少个文件。我不完全确定 \n 是由 bash 还是由 sed 分配。有人知道解决方法吗?

答案1

如果您只想排除ok项目,您可以这样做:

grep -cv ': ok$' <<< "$LIST"

或者类似,但大致相反

grep -c ':$' <<< "$LIST"

编辑:基于@ilkkachu 的评论

如果列表完全为空,则-vc由于某种行为,变体将错误地报告计数为 1 <<<

您可以添加一个防护来检测空列表,也可以简单地使用管道

printf "%s" "$LIST" | grep -vc ": ok$"

如果列表可能包含空行,那么在使用时也会导致计数错误-vc

在任何一种情况下,都可以进行进一步的修改以防止错误计数。

grep -vcE "^$|: ok$"

但现在我们开始跳过障碍,从而使代码更难理解。

答案2

Here-string<<<在将字符串传递给命令之前在字符串末尾添加换行符,而命令替换会在读取内部命令的输出后删除所有尾随换行符。

另外,echo在打印的内容中添加尾随换行符,但这不是这里的主要问题。

因此,假设变量中有一个完整的行,末尾有换行符,所以字符串是file1: ok<nl>

现在,您运行sed '/ok/d' <<< "$LIST",并在这里sed获取输入file1: ok<nl><nl>。它删除任何包含 的行ok,并输出空行<nl>

您可以通过命令替换来捕获它,该替换会删除尾随的换行符,给出空字符串。然后将其分配给NEWLIST.

然后,echo "$NEWLIST"打印一个换行符(因为echo添加了一个),并wc -l <<< "$NEWLIST"给出一个换行符作为输入wc(因为here-string添加了一个)。

如果变量最初只是file1: ok,没有尾随换行符,则仅命令替换不会从 sed 输出的末尾删除换行符,您将得到相同的最终结果。

所有这些意味着命令替换和此处字符串大多适用于单行值,您可能不希望换行符出现在变量中,但通常确实希望提供带有完整行作为输入的任何命令。正如您所看到的,它们也适用于多行字符串(如果变量中再次缺少最终换行符),但换行符的奇怪删除和添加仍然会发生。如果没有要删除的换行符,它就会崩溃。

要了解为什么要进行这种杂耍,请注意,如果命令替换没有从此处的输出中删除换行符date,则这会在末尾的句点之前打印一个换行符,从而打破中间的行。

$ weekday=$(date +%A)
$ echo "today is a $weekday."
today is a Thursday.

最简单的解决方法可能只是将多行数据存储在文件中。包含inputfile五行(带有适当的换行符):

file1: ok
file2: ok
file3: ok
file4: ok
file5: ok

然后:

tmpfile=$(mktemp)
sed -e '/ok/d' < inputfile > "$tmpfile"
wc -l < "$tmpfile"
rm -f "$tmpfile"

输出0

(请注意,\s或都不\+是标准的 POSIX 基本或扩展正则表达式语法,因此它们不适用于所有系统,例如 macOS sed。这就是我在/ok/上面使用的原因。)

答案3

我不完全确定 \n 是由 bash 还是由 sed 分配。

目前两者都不是。例如,您可以检查它

$ echo -n "$NEWLIST"  # echo without -n adds a newline
$ wc -l <<< ""
1
$ wc -c <<< ""
1

它是echo故意打印你的变量(它是空的)并以换行符结尾,对于这里字符串,Bash 确保它以换行符结尾,因为如果最后一行不以 \n 结尾,工具往往会表现得令人惊讶。

解决这个问题最简单的方法可能就是检查 NEWLIST 是否为空。或者直接更多地处理文件。例如:

list_file="$(mktemp)"
new_list_file="$(mktemp)"

# cleanup
trap 'rm "$list_file" "$new_list_file"' EXIT

echo "$LIST" > "$list_file"
sed '/:\s.\+$/d' "$list_file" > "$new_list_file"
wc -l "$new_list_file"
# or wc -l < "$new_list_file" if you want to prevent it from printing the filename

作为意外结果的示例,如果此处字符串不会添加换行符,请考虑以下命令的预期结果,然后运行它:

echo -n "Content" | wc -l

答案是:0

相关内容