Comando como `column -t` que mantém os separadores na saída


17

Estou editando uma tabela simples. Eu gostaria de tê-lo bem formatado. Embora eu possa usar tbl, latexou similar, isso parece exagero - o texto simples é realmente suficiente. Como é simples, é melhor que a fonte seja a saída. Portanto, a fonte também deve parecer boa. Parece que deve ser um trabalho perfeito para column -s '|' -t- ele encontra os separadores e insere espaços automaticamente para alinhar de acordo com a largura máxima em cada coluna. Infelizmente, ele exclui os separadores, por isso não posso executá-lo novamente depois de uma nova edição. Existe alguma boa ferramenta de processamento de texto que possa fazer isso idempotentemente, para que sua saída sirva de entrada? Ou preciso escrever o meu?

EDIT: aqui está um exemplo do que eu quero:

foo |   bar | baz
abc def | 12 | 23456

Deve se tornar

foo     | bar | baz
abc def | 12  | 3456

Quando ' 'é o separador e o espaçador, column -tfunciona bem. Mas meus itens têm espaços, então não posso usar isso. Ter os espaçadores distintos dos separadores complica as coisas. Eu acho que é útil tê-los tratados como caracteres separadores quando próximos a separadores, mas não é isso que column -s '|' -tacontece (embora obviamente o comportamento atual também seja útil).


Você pode usar o emacs org-mode. O suporte à tabela é realmente incrível, fornecendo funcionalidade semelhante a uma planilha.
Vschum

Não é tão geral quanto o que eu pensava ser razoável, mas há um programa python especificamente para tabelas de descontos em leancrew.com/all-this/2008/08/tables-for-markdown-and-textmate .
wnoise

Esse é um problema que eu me deparo pelo menos a cada duas semanas. A única solução viável para contornar o printfholocausto de cada vez, que eu encontrei até agora, é adicionar um caractere (como @) exclusivo aos dados e usá-lo ... | column -s@ -tposteriormente.
Sj #

Respostas:


17

Não sei se entendi direito qual é o seu problema. Mas, pode ser resolvido adicionando um separador temporal adicional? portanto, você pode usar o segundo separador para marcar as separações, mantendo o separador original intocado.

Veja este exemplo em que adiciono um "@" a cada um dos "|" portanto, a entrada do comando da coluna seria "xxx @ | aaaa". A coluna processará o "@" mantendo o "|" intocado:

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

Esperto. Quase faço o que quero e, de fato, faço o que pedi - deixa os separadores para dentro. Também quero que os espaços próximos aos separadores verdadeiros possam ser ajustados para baixo, em vez de apenas para cima, como aqui.
Wnoise

@wnoise: use sed 's/ *| */@| /g'vez
Stéphane Gimenez

@ Stéphane Gimenez: E adicionando sed 's/ |/|/g'após as columncorreções os espaços extras adicionados. Agora temos uma solução que funciona bem o suficiente para mim. (Apesar de que seria bom se não depender de um caractere extra assim que se um não está disponível.?)
wnoise

3
@noite: Em vez de @, você pode usar algo que normalmente não aparece no texto, como um valor ASCII baixo, por exemplo. $ '\ x01' ... (mas não $ '\ x00') ...
Peter.O

6

Isso não estava disponível quando você fez a pergunta, mas a partir da versão 2.23 column de util-linuxpermite selecionar o separador de saída via

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

Então, basta executar:

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

Observe que a util-linuxversão não está disponível no Ubuntu 18.04 (e provavelmente em outras distros derivadas de Debain) no momento em que este foi escrito. Somente a bsdmainutilsversão está disponível. A bsdmainutilsversão não suporta a formatação de saída.
Htaccess

5

Aqui está um script bash. Ele não usa 'column -t', e o separador é tratado exatamente como o IFS, porque é o IFS (ou pelo menos a versão interna do IFS do awk) ... O delimitador padrão é $ '\ t'

Este script preenche totalmente o campo mais à direita.
'column' não faz isso.
Ao preencher todas as colunas, esse script pode ser
facilmente modificado para criar também um quadro de tabela.

Nota. O arquivo de entrada precisa ser processado duas vezes
('coluna' também precisaria fazer isso).
A primeira passagem é obter as larguras máximas da coluna.
A segunda passagem é expandir os campos (por coluna)

Adicionadas algumas opções e corrigido um erro gritante (renomeando variáveis ​​:(

  • -l Espaço em branco à esquerda de qualquer campo recuado
  • -r Espaço em branco à direita, mais largo que o texto mais amplo (para a coluna)
  • -b Ambos -l e -r
  • -L Delimitador de saída esquerdo é adicionado
  • -R O delimitador de saída direito é adicionado
  • -B Ambos -L e -R
  • -S Escolha o separador de saída

#!/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

Bem feito. Obviamente, eu esperava algo que não fosse necessário escrever um novo programa.
Wnoise

2

Dê uma olhada no plugin vim chamado Tabularize

:Tabularize /<delim>

1

Este é um ajuste de duas passagens na resposta de hmontoliu , que evita a necessidade de codificar o delimitador, adivinhando-o a partir dos dados de entrada.

  1. analise a entrada para caracteres não alfanuméricos únicos cercados por espaços, classifique-os pelo que é mais comum e assuma que o caractere mais comum é o delimitador ao qual está atribuído $d.
  2. proceda mais ou menos como na resposta de hmonoliu , mas use um ASCII NULL como preenchimento, em vez de um @, conforme o comentário de PeterO .

O código é uma função que aceita um nome de arquivo, ou então entrada de 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 ;
}

Saída de algn foo(ou também algn < foo):

foo      | bar  | baz
abc def  | 12   | 23456

Observando isso um ano depois, parece que a chamada STDIN não pode e não deve funcionar, porque usa o STDIN duas vezes. Testar com arquivos grandes (cerca de 80 milhões de linhas) indica que aparentemente funciona corretamente. Hmm ...
agc 23/02

0

Idéia usada do hmontoliu para implementar o comando simples:

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

Comente:

  • ${1:-,}- é o primeiro argumento com ,como padrão
  • o primeiro sedinsere um símbolo intermediário ( $intermsegundo argumento ou ~por padrão)
  • em seguida, columnsubstitui símbolo intermediário com espaços que fazem alinhamento
  • o segundo sedlimpa os espaços redundantes após o columncomando

Exemplo de uso:

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

a   : bb: cccc
aaaa: b : cc

Também é bom porque é idempotente: você pode aplicá-lo várias vezes e obter o mesmo resultado (por exemplo, quando você edita no vim e realinhar).

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.