像`column -t`这样的命令,而是在输出中保留分隔符


17

我正在编辑一个简单的表。我想格式化好它。虽然我可以使用tbllatex或类似名称,但这似乎有些过分了-纯文本确实足够。因为很简单,所以我最好将源作为输出。因此来源也应该看起来不错。这似乎应该是一个完美的工作column -s '|' -t-它找到分隔符并自动插入空格以根据每列的最大宽度对齐。不幸的是,它删除了分隔符,因此我无法在进一步编辑后重新运行它。有没有什么好的文本处理工具可以幂等地做到这一点,以便将其输出用作输入?还是我需要自己写?

编辑:这是我想要的示例:

foo |   bar | baz
abc def | 12 | 23456

应该成为

foo     | bar | baz
abc def | 12  | 3456

' '隔板和隔板都column -t很好时。但是我的物品中有空格,所以我不能使用它。使隔离物与隔离物不同会使事情复杂化。我认为在分隔符旁边将它们视为分隔符很有用,但这不是做什么的column -s '|' -t(尽管显然当前的行为也很有用)。


您可以使用emacs org-mode。表格支持实际上是相当惊人的,提供了类似于电子表格的功能。
vschum 2011年

不像我认为的那样合理,但是在leancrew.com/all-this/2008/08/tables-for-markdown-and-textmate上有一个专门用于markdown表的python程序。
wnoise

我至少每两周遇到一次这个问题。printf到目前为止,我发现唯一可行的绕过大屠杀的解决方案是@在数据中添加一个唯一的char(如),然后再使用... | column -s@ -t
sjas 2013年

Answers:


17

不知道我是否理解正确,您的问题是什么。但是,是否可以通过添加其他时间分隔符来解决?因此,您可以使用第二个分隔符标记分隔,并保持原始分隔符不变。

看到这个例子,我在每个“ |”中添加一个“ @” 因此column命令的输入为“ xxx @ | yyyy”。列将处理“ @”并保留“ |” 不变的:

~$ echo "foo | this is some text | bar" | sed 's/|/@|/g'  | column -s '@' -t
foo   | this is some text   | bar

聪明。几乎按照我的要求做的,实际上也按照我的要求做的-留下分隔符。我还希望真正分隔符旁边的空间能够向下调整,而不是像此处那样向上调整。
wnoise

@wnoise:使用sed 's/ *| */@| /g'代替
斯特凡希门尼斯

@StéphaneGimenez:sed 's/ |/|/g'column修复后添加额外的空格。我们现在有一个对我来说足够好的解决方案。(尽管它不依赖于这样的额外字符会很好。如果一个人不可用怎么办?)
wnoise

3
@wnoise:您可以使用通常不会出现在文本中的内容(例如,低ASCII值)代替@。$'\ x01'...(但不是$'\ x00')...
Peter.O 2011年

6

当您提出问题时,此功能不可用,但是从2.23版 column开始util-linux,您可以通过以下命令选择输出分隔符

   -o, --output-separator string
          Specify the columns delimiter for table output (default is two spaces).

所以只需运行:

 column -s '|' -o '|' -t infile

请注意,在util-linux撰写本文时,该版本在Ubuntu 18.04(以及其他Debain衍生发行版)上不可用。仅bsdmainutils版本可用。该bsdmainutils版本不支持输出格式。
htaccess

5

这是一个bash脚本。它不使用'column -t',并且分隔符的处理方式与IFS完全相同,因为它是IFS(或至少是awk的IFS的内部版本)...默认分隔符为$'\ t'

该脚本完全填充了最右边的字段。
“列”不执行此操作。
通过填充所有列,可以
轻松修改此脚本以创建表框架。

注意。输入文件需要处理两次
(“ column”也需要这样做)
。第一步是获取列的最大宽度。
第二遍是扩展字段(每列)

添加了一些选项并修复了一个明显的错误(重命名变量:(

  • -l任何缩进字段的左修剪空白
  • -r右切空格比最宽的文本宽(对于列)
  • -b -l和-r
  • -L添加左输出定界符
  • -R右输出定界符已添加
  • -B -L和-R
  • -S选择输出分隔符

#!/bin/bash
#
#   script [-F sep] [file]
#
#   If file is not specified, stdin is read 
#    
# ARGS ######################################################################
l=;r=;L=;R=;O=;F=' ' # defaults
for ((i=1;i<=${#@};i++)) ;do
  case "$1" in
    -- ) shift 1;((i--));break ;;
    -l ) l="-l";shift 1;((i-=1)) ;;        #  left strip whitespace
    -r ) r="-r";shift 1;((i-=1)) ;;        # right strip whitespace
    -b ) l="-l";r="-r";shift 1;((i-=1)) ;; # strip  both -l and -r whitespace
    -L ) L="-L";shift 1;((i-=1)) ;;        #  Left output delimiter is added
    -R ) R="-R";shift 1;((i-=1)) ;;        # Right output delimiter is added
    -B ) L="-L";R="-R";shift 1;((i-=1)) ;; # output Both -L and -R delimiters
    -F ) F="$2";shift 2;((i-=2)) ;; # source separator
    -O ) O="$2";shift 2;((i-=2)) ;; # output  separator. Default = 1st char of -F 
    -* ) echo "ERROR: invalid option: $1" 1>&2; exit 1 ;;
     * ) break ;;
  esac
done
#
if  [[ -z "$1" ]] ;then # no filename, so read stdin
  f="$(mktemp)"
  ifs="$IFS"; IFS=$'\n'; set -f # Disable pathname expansion (globbing)
  while read -r line; do
    printf "%s\n" "$line" >>"$f"
  done
  IFS="$ifs"; set +f # re-enable pathname expansion (globbing)
else
  f="$1"
fi
[[ -f "$f" ]] || { echo "ERROR: Input file NOT found:" ;echo "$f" ;exit 2 ; }
[[ -z "$F" ]] && F=' '        # input Field Separator string
[[ -z "$O" ]] && O="$F"       # output Field Separator
                 O="${O:0:1}" #   use  single char only

# MAIN ######################################################################
max="$( # get max length of each field/column, and output them
  awk -vl="$l" -vr="$r" -vL="$L" -vR="$R" -vF="$F" -vO="$O" '
    BEGIN { if (F!="") FS=F }
    { for (i=1;i<=NF;i++) { 
        if (l=="-l") { sub("^[ \t]*","",$i) }
        if (r=="-r") { sub("[ \t]*$","",$i) }
        len=length($i); if (len>max[i]) { max[i]=len } 
        if (i>imax) { imax=i } 
      } 
    }
    END { for(i=1;i<=imax;i++) { printf("%s ",max[i]) } }
  ' "$f" 
)"

awk -vl="$l" -vr="$r" -vL="$L" -vR="$R" -vF="$F" -vO="$O" -v_max="$max" '
  BEGIN { if (F!="") FS=F; cols=split(_max,max," ") }
  { # Bring each field up to max len and output with delimiter
    printf("%s",L=="-L"?O:"")
    for(i=1;i<=cols;i++) { if (l=="-l") { sub("^[ \t]*","",$i) } 
                           if (r=="-r") { sub("[ \t]*$","",$i) }
      printf("%s%"(max[i]-length($i))"s%s",$i,"",i==cols?"":O) 
    } 
    printf("%s\n",R=="-R"?O:"")
  }
' "$f"

# END #######################################################################    
if  [[ -z "$1" ]] ;then # no filename, so stdin was used
  rm "$f"   # delete temp file
fi
exit

做得很好。当然,我希望实际上不需要编写新程序。
wnoise 2011年


1

这是对hmontoliu的答案的两遍调整,通过从输入数据中猜测分隔符来避免对分隔符进行硬编码。

  1. 解析由空格包围的单个非字母数字字符的输入,对最常见的字符进行排序,并假定最常见的字符是定界符,并将其分配给$d
  2. 按照hmonoliu的答案进行或多或少的处理,但使用ASCII NULL作为填充,而不是@根据PeterO的注释使用。

该代码是一个接受文件名或从STDIN输入的函数:

algn() { 
    d="$(grep -ow '[^[:alnum:]]' "${1:-/dev/stdin}"  | \
         sort | uniq -c | sort -rn | sed -n '1s/.*\(.$\)/\1/p')" ;
    sed "s/ *$d */\x01$d /g" "${1:-/dev/stdin}"  | column -s $'\001' -t ;
}

的输出algn foo(或algn < foo):

foo      | bar  | baz
abc def  | 12   | 23456

一年后的这一年,看来STDIN调用不能而且不应该工作,因为它两次用尽了STDIN。使用大文件(大约8000万行)进行测试表明它显然可以正常工作。嗯...
agc

0

使用hmontoliu的想法来实现简单的命令:

#! /bin/bash
delim="${1:-,}"
interm="${2:-\~}"
sed "s/$delim/$interm$delim/g" | column -t -s "$interm" | sed "s/  $delim/$delim/g"

评论:

  • ${1:-,}-是,默认的第一个参数
  • 第一个sed插入中间符号($interm第二个参数或~默认情况下)
  • 然后column将中间符号替换为可以对齐的空格
  • 第二个命令sed后清理多余的空间column

用法示例:

$ echo "
a: bb: cccc
aaaa: b : cc
" | align :

a   : bb: cccc
aaaa: b : cc

它是幂等的,这也很好:您可以多次应用它并获得相同的结果(例如,在vim中编辑并重新对齐时)。

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.