笔记

笔记

我想bash使用变量中的文件路径读取脚本中的多行文件,然后使用多字符分隔符合并行并将结果保存到另一个变量。

我想跳过空白行和尾随新行,并且不需要尾随分隔符。

此外,我想支持\r\n- 如果没有进一步的“成本” - 为什么不也\r作为换行符(当然\n)。

该脚本应在 RHEL 上运行,使用 GNU 的 bash 4.2.46、sed 4.2.2、awk 4.0.2、grep 2.20、coreutils 8.22(tr、cat、paste、sort、cut、head、tail、tee...), xargs 4.5.11 和 libc 2.17 以及 perl 5.16.3、python 2.7.5 和 openjdk 11.0.8。

它应该每天对 ca 的文件运行大约两次。在一台像样的机器/虚拟机上有 10 行。如果可读性、可维护性和简洁性不会受到太大影响,我非常愿意接受更高性能的解决方案。

Win7可以在同一台计算机或其他系统上创建和修改要读取的文件Win10

到目前为止我的方法是

joined_string_var=$(sed 's/\r/\n/g' $filepathvar | grep . | sed ':a; N; $!ba; s/\n/; /g')
  • 因此,首先我替换\r\n覆盖所有换行格式并使输出对于 grep 可读。

  • 然后我删除空行grep .

  • 最后我使用 sed 进行实际的行合并。

我在第一步中使用了sednot来避免使用 cat,但我不太确定我是否更喜欢这样:tr

joined_string_var=$(cat $filepathvar | tr '\r' '\n' | grep . | sed ':a; N; $!ba; s/\n/; /g')

更新:我不知何故完全错过了简单的重定向:

joined_string_var=$(tr '\r' '\n' <$filepathvar | grep . | sed ':a; N; $!ba; s/\n/; /g')

有什么想法可以更优雅地完成此操作(更少的命令,更好的性能,简洁性和可读性也不会差很多)?

答案1

优雅可能来自正确的正则表达式。您可以将每个行终止符, ,转换为您想要的分隔符,而不是将 every 更改\r\n( ) (在 GNU sed 中,很少有 sed 实现能够理解,但并非所有 sed 实现都能理解):s/\r/\n/g\r\n\r\n\r-E

sed -E 's/\r\n|\r|\n/; /g'

或者,如果您想删除空行,任何跑步此类行终止符:

sed -E 's/[\r\n]+/; /g'

如果我们能够捕获模式空间中的所有行终止符,那么这将起作用。这意味着将整个文件放入内存中以便能够编辑它们。

因此,您可以使用更简单的方法(GNU sed 的一个命令):

sed -zE 's/[\r\n]+/; /g; s/; $/\n/' "$filepathvar"

采用-z空字节作为行终止符,有效地获取模式空间中的所有\r和。\n

将所有类型的行分隔符转换s/[\r\n]+/; /g为您想要的字符串。

将(最后一个)尾随分隔符转换 s/; $/\n/为实际的换行符。


笔记

sed选项-z意味着使用零分隔符 (0x00)。使用该分隔符是因为 find 需要能够处理带有换行符 ( -print0) 的文件名,该换行符将与 xargs ( -0) 选项匹配。这意味着一些工具也经过修改以处理零分隔字符串。

这是一个非 posix 选项,它会在零处而不是换行符处中断文件。

Posix 文本文件必须没有零 (NIL) 字节,因此使用该选项实际上意味着在处理之前将整个文件捕获到内存中。

在 NIL 上破坏文件意味着换行符最终可以在 sed 的模式空间上编辑。如果文件碰巧有一些 NIL 字节,这个想法对于换行符仍然有效,因为它们在文件的每个块中仍然是可编辑的。

-z选项已添加到 GNU sed 中。 ATT sed(posix 所基于的)没有这样的选项(现在仍然没有),一些 BSD sed 也仍然没有。

该选项的替代方法-z是捕获内存中的整个文件。这可以通过某些方式来完成:

sed 'H;1h;$!d'          # capture whole file in hold space.
sed ':a;N;$!ba'         # capture whole file in pattern space.

将所有换行符(最后一个换行符除外)放在模式空间中使得可以编辑它们:

sed -Ee 'H;1h;$!d;x'   -e 's/(\r\n|\r|\n)/; /g

对于较旧的 sed,还需要使用更长且更明确的 sed,(\r\n|\r|\n)+而不是[\r\n]+因为此类 sed 不理解\r\n位于括号内的表达式[]

线路导向

一次运行一行的解决方案(\r在此解决方案中 a 也是有效的行终止符),这意味着使用 GNU awk 无需将整个文件保留在内存中(使用的内存较少):

awk -vRS='[\r\n]+' 'NR>1{printf "; "}{printf $0}END{print ""}'  file

由于正则表达式记录分隔符,必须是 GNU awk [\r\n]+。在其他 awk 中,记录分隔符必须是单个字节。

答案2

只需使用perl. Sed 与换行符的使用比较复杂,但 Perl 可以轻松处理它们:

printf 'aa\nbb\ncc\n' > file
printf 'aa2\r\nbb2\r\ncc2\r\n' > file2
printf 'aa3\rbb3\rcc3\r' > file3

所以,file\n行结尾,file2\r\nfile3\r(顺便说一句,现在已经过时了,支持它没有多大意义)。现在,将它们连接成一个字符串:

$ joined_string_var=$(perl -pe 's/(\r\n|\r|\n)/; /g' file file2 file3)
$ echo "$joined_string_var"
aa; bb; cc; aa2; bb2; cc2; aa3; bb3; cc3; 

不过,您需要第二遍来删除尾随;分隔符:

$ joined_string_var=$(perl -pe 's/(\r\n|\r|\n)/; /g' file file2 file3 | sed 's/; $//')
$ echo "$joined_string_var" 
aa; bb; cc; aa2; bb2; cc2; aa3; bb3; cc3

或者,在 perl 中删除它:

$ joined_string_var=$(perl -ne 's/(\r\n|\r|\n)/; /g; $k.=$_; END{$k=~s/; $//; print $k}' file file2 file3)
$ echo "$joined_string_var" 
aa; bb; cc; aa2; bb2; cc2; aa3; bb3; cc3

答案3

为了记录zsh(对于那些有类似要求但没有bash限制的人),您应该这样做:

IFS=$'\r\n'
joined=${(j[; ])$(<$filepathvar):#}
  • IFS=$'\r\n'将单词分割的字段分隔符设置为 CR 或 LF 字符(使用 ksh93 样式$'...'引号)。
  • $(<file): 就像 inksh扩展到内容file(没有尾随换行符),受分词的影响。
  • ${list:#pattern}扩展为列表中与 不匹配的元素pattern(以及kshs的扩展名${list#pattern})。这里用空字符串作为模式来删除空行。
  • ${(j[; ])list} joins 列表的元素"; "

答案4

f=file
python3 -c "import re
print(re.sub(r'[\r\n]+', '; ', open('$f').read().strip('\r').strip('\n')))"
perl -nF'[\r\n]+' -0777E '$,="; ";
  say @F;
' file

相关内容