我想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 进行实际的行合并。
我在第一步中使用了sed
not来避免使用 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\n
和file3
有\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
(以及ksh
s的扩展名${list#pattern}
)。这里用空字符串作为模式来删除空行。${(j[; ])list}
j
oins 列表的元素"; "
。
答案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