【问题标题】:Indent long line stdout缩进长行标准输出
【发布时间】:2019-05-03 08:56:19
【问题描述】:

假设我有一个标准的 80 列终端,执行带有长行输出的命令(即来自ls 的标准输出),该命令分为两行或多行,并且想要缩进我所有 bash 标准输出的续行。

缩进应该是可配置的,1 或 2 或 3 或任何空格。

从这里

lrwxrwxrwx 1 root root 24 Feb 19 1970 sdcard -> /storage/emula
ted/legacy/

到这里

lrwxrwxrwx 1 root root 24 Feb 19 1970 sdcard -> /storage/emula
ted/legacy/

阅读此Indenting multi-line output in a shell script,因此我尝试通过管道传递| sed 's/^/ /',但给了我完全相反的结果,缩进第一行而不是续行。

理想情况下,每次打开交互式 shell 并执行任何命令时,我都会将脚本放在 profile.rc 中或其他任何地方,长输出会缩进。

【问题讨论】:

  • awk 似乎是最简单的方法 - 考虑添加该标签。您可以使用tput cols 获取终端的宽度并使用awk 脚本(如awk w=$(tput cols) 'length($0)>w{print substr($0,1,w);print "____",substr($0,w+1);next}1')进行过滤,然后将其放入bash 函数中以方便使用。
  • 只需将输出通过管道传输到column -t

标签: bash awk sed stdout interactive


【解决方案1】:

我会为此使用

awk -v width="$COLUMNS" -v spaces=4 '
BEGIN {
  pad = sprintf("%*s", spaces, "") # generate `spaces` spaces
}
NF {                               # if current line is not empty
  while (length > width) {         # while length of current line is greater than `width`
    print substr($0, 1, width)     # print `width` chars from the beginning of it
    $0 = pad substr($0, width + 1) # and leave `pad` + remaining chars
  }
  if ($0 != "")                    # if there are remaining chars
    print                          # print them too 
  next
} 1' file

一行:

awk -v w="$COLUMNS" -v s=4 'BEGIN{p=sprintf("%*s",s,"")} NF{while(length>w){print substr($0,1,w);$0=p substr($0,w+1)} if($0!="") print;next} 1'

正如@Mark 在 cmets 中建议的那样,您可以将其放在一个函数中并将其添加到 .bashrc 以方便使用。

function wrap() {
  awk -v w="$COLUMNS" -v s=4 'BEGIN{p=sprintf("%*s",s,"")} NF{while(length>w){print substr($0,1,w);$0=p substr($0,w+1)} if($0!="") print;next} 1'
}

用法:

ls -l | wrap

由 Ed Morton 根据请求编辑:

与上面的 oguzismails 脚本非常相似,但应该适用于 Busybox 或任何其他 awk:

$ cat tst.awk
BEGIN { pad = sprintf("%" spaces "s","") }
{
    while ( length($0) > width ) {
        printf "%s", substr($0,1,width)
        $0 = substr($0,width+1)
        if ( $0 != "" ) {
            print ""
            $0 = pad $0
        }
    }
    print
}
$
$ echo '123456789012345678901234567890' | awk -v spaces=3 -v width=30 -f tst.awk
123456789012345678901234567890
$ echo '123456789012345678901234567890' | awk -v spaces=3 -v width=15 -f tst.awk
123456789012345
   678901234567
   890
$ echo '' | awk -v spaces=3 -v width=15 -f tst.awk

$

第一个测试用例表明您没有在全角输入行之后打印空白行,第三个测试用例表明它不会删除空白行。通常我会使用sprintf("%*s",spaces,"") 来创建pad 字符串,但我在评论中看到这在您使用的明显非POSIX awk 中不起作用。

【讨论】:

  • 由于 bash 被标记,您可能可以:awk -v width=$COLUMNS ...?
  • 我收到了意外的令牌,Android 4.4 busybox 1.30.1
  • @JuTutt 如果可以选择安装符合 POSIX 的 awk。否则,请参阅 Ed Morton 对我的回答的编辑。
  • 试图在单行函数中实现但没有运气awk -v spaces=3 -v width=80 'BEGIN{pad=sprintf("%",spaces,"s","")} {while(length($0)>width){printf "%s",substr($0,1,width);$0=substr($0,width+1)};if($0!=""){print "" $0=pad $0}print}'
  • 还在 .bashrc alias ll='ls -Al | wrap' 中创建了别名,与第一个 @oguzismail 一起使用,有没有办法将此 wrap 函数附加到所有命令而不为每个人创建别名?
【解决方案2】:

这可能对你有用(GNU sed):

sed 's/./&\n  /80;P;D' file

这会将行拆分为长度为 80 的行,并将以下行缩进 2 个空格。

或者,如果您愿意:

s='  ' c=80
sed "s/./&\n$s/$c;P;D" file

要防止打印空行,请使用:

sed 's/./&\n/80;s/\n$//;s/\n    /;P;D' file

或更容易:

sed 's/./\n    &/81;P;D' file

【讨论】:

  • 这将删除空行或所有空白字符的行。顺便说一句,您在第一个脚本的末尾缺少 '
【解决方案3】:

使用纯 bash 字符串操作的一种可能解决方案。 您可以使脚本读取stdin 并格式化它读取的任何内容。

MAX_LEN=5  # length of the first/longest line
IND_LEN=2  # number of indentation spaces
short_len="$((MAX_LEN-IND_LEN))"
while read line; do
    printf "${line:0:$MAX_LEN}\n"
    for i in $(seq $MAX_LEN $short_len ${#line}); do
        printf "%*s${line:$i:$short_len}\n" $IND_LEN
    done
done

用法:(假设脚本保存为indent.sh

$ echo '0123456789' | ./indent.sh
01234
  567
  89

【讨论】:

  • 请参阅unix.stackexchange.com/q/169716/133219 了解不这样做的一些原因(包括它会非常慢,会损坏文本,会产生神秘的语法错误,会根据目录的内容而有所不同您在哪里运行它,并且存在安全问题)。
  • 一些特定的 cmets:使用小写或混合大小写的变量名,以避免与环境或 shell 变量名发生冲突的风险。除非必须,否则不要使用seq。改用 C 风格的 for 循环:for ((i=max_len; i<=${#line}; i+=ind_len))。不要将变量放在 printf 的格式参数中:printf '%*s%s\n' "$ind_len" '' "${line:$i:$short_len}" 你可以这样算:(( short_len = max_len - ind_len )) 我觉得看起来更干净。