Comando como `column -t` que en su lugar mantiene separadores en la salida


17

Estoy editando una tabla simple. Me gustaría tener un buen formato. Si bien podría usar tbl, latexo similar, esto parece excesivo: el texto sin formato realmente es suficiente. Como es simple, también podría tener la fuente como la salida. Entonces la fuente también debería verse bien. Parece que debería ser un trabajo perfecto column -s '|' -t: encuentra los separadores e inserta automáticamente espacios para alinearlos de acuerdo con el ancho máximo en cada columna. Desafortunadamente, elimina los separadores, por lo que no puedo volver a ejecutarlo después de una edición posterior. ¿Existe alguna buena herramienta de procesamiento de texto que pueda hacer esto de manera idempotente, de modo que su salida sirva como entrada? ¿O necesito escribir el mío?

EDITAR: aquí hay un ejemplo de lo que quiero:

foo |   bar | baz
abc def | 12 | 23456

debe convertirse

foo     | bar | baz
abc def | 12  | 3456

Cuando ' 'es tanto el separador como el espaciador, column -tfunciona bien. Pero mis artículos tienen espacios, así que no puedo usar eso. Tener los espaciadores distintos de los separadores complica las cosas. Creo que es útil que sean tratados como caracteres separadores cuando están al lado de los separadores, pero eso no es lo que column -s '|' -thace (aunque obviamente el comportamiento actual también es útil).


Podrías usar emacs org-mode. El soporte de la mesa es realmente sorprendente, ya que proporciona una funcionalidad similar a la hoja de cálculo.
vschum

No es tan general como lo que pensé que sería razonable, pero hay un programa de Python específicamente para tablas de descuento en leancrew.com/all-this/2008/08/tables-for-markdown-and-textmate .
wnoise

Este es un problema con el que me encuentro al menos cada dos semanas. La única solución viable para evitar el printfholocausto cada vez, que he encontrado hasta ahora, es agregar un carácter único (like @) en los datos y usarlo ... | column -s@ -tdespués.
sjas

Respuestas:


17

No estoy seguro si entiendo bien cuál es su problema. Pero, ¿se puede resolver agregando un separador temporal adicional? por lo tanto, puede usar el segundo separador para marcar las separaciones, manteniendo intacto el separador original.

Vea este ejemplo donde agrego una "@" a cada una de las "|" entonces la entrada del comando de columna sería "xxx @ | aaaa". La columna procesará la "@" manteniendo la "|" intacto:

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

Inteligente. Casi hace lo que quiero, y de hecho hace lo que le pedí: deja los separadores adentro. También quiero que los espacios al lado de los separadores verdaderos se puedan ajustar hacia abajo, en lugar de solo hacia arriba, como aquí.
wnoise

@wnoise: use sed 's/ *| */@| /g'en su lugar
Stéphane Gimenez

@ Stéphane Gimenez: Y agregando sed 's/ |/|/g'después de las columncorrecciones, se agregaron los espacios adicionales. Ahora tenemos una solución que funciona lo suficientemente bien para mí. (. A pesar de que sería bueno si no dependía de un carácter adicional como esto ¿Qué pasa si uno no está disponible?)
wnoise

3
@wnoise: en lugar de @, puede usar algo que normalmente no aparece en el texto, como un valor ASCII bajo, por ejemplo. $ '\ x01' ... (pero no $ '\ x00') ...
Peter.O

6

Esto no estaba disponible cuando hizo la pregunta, pero a partir de la versión 2.23 column de le util-linuxpermite seleccionar el separador de salida a través de

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

Así que simplemente ejecuta:

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

Tenga en cuenta que la util-linuxversión no está disponible en Ubuntu 18.04 (y probablemente otras distribuciones derivadas de Debain) en el momento de la escritura. Solo la bsdmainutilsversión está disponible. La bsdmainutilsversión no admite el formato de salida.
htaccess

5

Aquí hay un script bash. No usa 'column -t`, y el separador se maneja exactamente como el IFS, porque es el IFS (o al menos, la versión interna de awk del IFS) ... El delimitador predeterminado es $' \ t '

Este script completa completamente el campo de la derecha.
'columna' no hace esto.
Al rellenar todas las columnas, este script se puede
modificar fácilmente para crear un marco de tabla también.

Nota. El archivo de entrada debe procesarse dos veces
('columna' también necesitaría hacer esto)
El primer paso es obtener los anchos máximos de columna.
El segundo paso es expandir los campos (por columna)

Se agregaron algunas opciones y se corrigió un error evidente (cambio de nombre de las variables :(

  • -l Espacio en blanco de recorte izquierdo de cualquier campo sangrado
  • -r Espacio en blanco de recorte derecho más ancho que el texto más ancho (para la columna)
  • -b Tanto -l como -r
  • -L Se agrega delimitador de salida izquierdo
  • -R Se agrega delimitador de salida derecho
  • -B Ambos -L y -R
  • -S Elegir separador de salida

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

Bien hecho. Por supuesto, esperaba algo que realmente no requiriera escribir un nuevo programa.
wnoise

2

Eche un vistazo al complemento vim llamado Tabularize

:Tabularize /<delim>

1

Este es un ajuste de dos pasos en la respuesta de hmontoliu , que evita la necesidad de codificar el delimitador, adivinándolo a partir de los datos de entrada.

  1. analizar la entrada de caracteres no alfanuméricos individuales rodeados de espacios, ordenarlos según cuál sea el más común y asumir que el carácter más común es el delimitador, que se asigna a $d .
  2. proceda más o menos como en la respuesta de hmonoliu , pero use un ASCII NULL como relleno, en lugar de un @, según el comentario de PeterO .

El código es una función que acepta un nombre de archivo o, de lo contrario, ingresa desde 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 ;
}

Salida de algn foo(o también algn < foo):

foo      | bar  | baz
abc def  | 12   | 23456

Mirando esto un año después, parece que la invocación de STDIN no puede y no debería funcionar porque usa STDIN dos veces. Las pruebas con archivos grandes (alrededor de 80 millones de líneas) indican que aparentemente funciona correctamente. Hmm ...
agc

0

Idea usada de hmontoliu para implementar un comando simple:

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

Comentario:

  • ${1:-,}- es un primer argumento con ,por defecto
  • el primero sedinserta un símbolo intermedio ( $intermsegundo argumento o ~por defecto)
  • luego columnreemplaza el símbolo intermedio con espacios que alinean
  • el segundo sedlimpia los espacios redundantes después del columncomando

Ejemplo de uso:

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

a   : bb: cccc
aaaa: b : cc

También es bueno porque es idempotente: puede aplicarlo varias veces y obtener el mismo resultado (por ejemplo, cuando edita en vim y realinear).

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.