sed输出如何像printf的格式化打印一样格式化?


7

sed可以用类似于printf的格式化打印格式的字符串替换文本吗?

以下sed命令用变量中指定的多个值替换以“ $ domain”的当前值开头的行。

/bin/sed  "s/\(^${domain} *${limittype} * ${limititem}.*\)/$EXPL#\1\n${domain} ${limittype} ${limititem} ${value}/" /etc/security/limits.conf

但是,由于domain等的值的长度不同,因此无法正确对齐输出。

因此输出将类似于

#oracle   hard   nproc    131072
oracle hard nproc 666

虽然有效,但很难阅读。我宁愿得到像

#oracle   hard   nproc    131072
oracle   hard   nproc    666

我可以拿到的最好的输出是:

/bin/sed  "s/\(^${domain}\)\( *\)\(${limittype}\)\( *\)\(${limititem}\)\( *\)\(.*\)/$EXPL#\1\2\3\4\5\6\7\n${domain}\2${limittype}\4${limititem}\6${value}/" /etc/security/limits.conf

但是我相信必须有一种更优雅的方法来做到这一点。

所述sed的一个衬里文件包含一些实例中使用指定数目的字符,例如

sed -e :a -e 's/^.\{1,78\}$/ &/;ta'  # set at 78 plus 1 space

但这regexp不在本replacement节中。


用制表符代替空格?
凯文(Kevin)

您能否提供示例输入部分及其相应的首选输出?你$EXPL#\1\n好像有点不对劲
Mikel 2012年

sed不能以任何方式是可行的做到这一点。如果你能提供这个问题的一个更清楚的描述,很多人在这里可以通过提供一种替代解决方案bashawk或任意数量的其他工具。
Mikel 2012年

Answers:


4

这使用扩展的regex语法-r,可以消除很多混乱情况。另外,由于您已经知道一些字段值,因此实际上不需要反向引用它们,从而再次减少了混乱(和开销)。

&是一个特殊的替换值:它包含整个匹配的模式。使用&,同样可以减少混乱。由于它不是反向引用,因此开销大大减少。

我用( +)对比( *)。在+假定有输入字段之间的至少一个空间。只是将其更改为*事实并非如此。

EXPL=
dom=oracle
typ=hard
itm=nproc
val=666

echo "oracle   hard   nproc    131072" |
  sed -r "s/^$dom( +)$typ( +)$itm( +).*/$EXPL#&\n$dom\1$typ\2$itm\3$val/" 

输出

#oracle   hard   nproc    131072
oracle   hard   nproc    666

大!正是我想要的,感谢您对的解释&
布拉姆2012年

6

从理论上讲,您可以完全在sed中完成此操作(因为它是图灵完成的),但这不是完成此任务的正确工具。

一种简单的方法是在sed中插入制表符,然后将其后处理为空格。如果可以确定所有列的位置,则将sed输出通过管道传输expand

</etc/security/limits.conf \
sed  "s/\(^${domain} *${limittype} * ${limititem}.*\)/$EXPL#\1\n${domain}\t${limittype}\t${limititem}\t${value}/" |
expand -t 10,17,26

\t如果您的sed不支持,请使用原义的制表符,而不是\t。)

如果您事先不知道列宽,请尝试使用BSD column实用程序。它查看整个输入文件以确定适合所有行长度的列宽。

</etc/security/limits.conf \
sed  "s/\(^${domain} *${limittype} * ${limititem}.*\)/$EXPL#\1 ${domain} ${limittype} ${limititem} ${value}/" |
column -t

如果您的sed脚本同时重写了注释掉的行和未注释掉的行,或者如果使用column,则需要一些后期处理以使注释掉的行倾斜注释标记的宽度。

… | sed '/^#/ s/ //'

您可以改用awk。它具有printf功能。另外,还有一种简单的方法可以保护特殊字符,例如.*在搜索的列内容中。

</etc/security/limits.conf awk -v domain="$domain" -v limittype="$limittype" -v limititem="$limititem" -v value="$value" '
$1 == domain && $2 == limittype && $3 == limititem  {
    printf "#%-9s %-8s %-9s %s\n%-9s %-8s %-9s %s\n", $1, $2, $3, $4, $1, $2, $3, value; next
}
1 {print}
'

或者可能column -t
Mikel 2012年

我会稍微练习一下awk版本,就像做练习一样,但是现在我无法正常工作。感谢您的精心解答!
布拉姆2012年

@Mikel哦,谢谢您提醒我。我的意思是说“如果您知道宽度,则扩大;如果您不知道宽度,则纵列”,而忘了写一个段落。
Gilles 2012年

@Bram我错过了awk代码片段中的一些内容,如果输入不匹配,则将其打印出来,请立即尝试。
Gilles 2012年

@Gilles:我编辑了答案,删除了对值的检查,因为$value其中包含新值,并且建议的版本永远不会与一行匹配。我还添加了额外的输入行,因此将写出注释以显示旧值。再次感谢。
布拉姆2012年

3

只需使用printf格式化sed输出即可:

printf "%5s %12s %4s\n" $(sed 's/.../.../')

1
我已经看到人们将printf嵌入到sed中,sed 's/xxx/$(printf "%5s %12s %4s\n")yyy/'但是它是否对每个处理的行都执行子过程?如果是这样,那么在awk中处理OP问题,使用它的printf功能可能更有意义。好运来所有
shellter
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.