Comando como 'column -t' que, ao contrário, mantém separadores na saída

17

Estou editando uma tabela simples. Eu gostaria de tê-lo bem formatado. Embora eu possa usar tbl , latex ou similar, isso parece um exagero - o texto simples é realmente suficiente. Como é simples, é melhor que a fonte seja a saída. Então a fonte deve ficar bem também. Isto parece que deveria 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, exclui os separadores, por isso não posso executá-lo depois de mais edições. Existe alguma boa ferramenta de processamento de texto que possa fazer isso de forma idêmica, para que sua saída sirva como entrada? Ou eu preciso escrever o meu próprio?

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 -t funciona 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 que eles sejam tratados como caracteres separadores ao lado de separadores, mas não é isso que o column -s '|' -t faz (embora obviamente o comportamento atual também seja útil).

    
por wnoise 14.08.2011 / 06:41

6 respostas

17

Não tenho certeza se entendi bem qual é o seu problema. Mas, isso 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 onde adiciono um "@" a cada um dos "|" então a entrada do comando de coluna seria "xxx @ | yyyy". 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

Espero que isso ajude

    
por 14.08.2011 / 10:55
6

Isso não estava disponível quando você fez a pergunta, mas a partir do verso 2.23 column de util-linux permite que você selecione 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
    
por 03.02.2017 / 14:52
5

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

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

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

Adicionado algumas opções e corrigido um bug gritante (renomeando variáveis :(

  • -l Espaços em branco à esquerda de qualquer campo recuado
  • -r Espaços em branco no canto direito mais largos que o texto mais largo (para a coluna)
  • -b Ambos -l e -r
  • -L O 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
    
por 14.08.2011 / 23:14
2

Dê uma olhada no plugin vim chamado Tabularize

:Tabularize /<delim>
    
por 27.09.2013 / 13:11
1

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

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

O código é uma função que aceita um nome de arquivo ou outra entrada de STDIN :

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

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

algn() { 
    d="$(grep -ow '[^[:alnum:]]' "${1:-/dev/stdin}"  | \
         sort | uniq -c | sort -rn | sed -n '1s/.*\(.$\)//p')" ;
    sed "s/ *$d */\x01$d /g" "${1:-/dev/stdin}"  | column -s $'
foo      | bar  | baz
abc def  | 12   | 23456
1' -t ; }
    
por 03.02.2017 / 18:25
0

Usamos a ideia de hmontoliu para implementar um comando simples:

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

Comentário:

  • ${1:-,} - é o primeiro argumento com , como padrão
  • o primeiro sed insere um símbolo intermediário ( $interm 2º argumento ou ~ por padrão)
  • , em seguida, column substitui o símbolo intermediário por espaços que fazem o alinhamento
  • o segundo sed limpa os espaços redundantes após column command

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 realinha).

    
por 03.02.2017 / 14:25