¿Cómo puedo alinear las columnas de las tablas en Bash?

86

Me gustaría generar un texto en formato de tabla. Lo que intenté hacer fue hacer eco de los elementos de una matriz con '\ t', pero estaba desalineado.

Mi código

for((i=0;i<array_size;i++));
do
   echo stringarray[$i] $'\t' numberarray[$i] $'\t' anotherfieldarray[$i]
done;

Mi salida

a very long string..........     112232432      anotherfield
a smaller string         123124343     anotherfield

Salida deseada

a very long string..........     112232432      anotherfield
a smaller string                 123124343      anotherfield
usuario1709294
fuente

Respuestas:

95

printf es genial, pero la gente lo olvida.

$ for num in 1 10 100 1000 10000 100000 1000000; do printf "%10s %s\n" $num "foobar"; done
         1 foobar
        10 foobar
       100 foobar
      1000 foobar
     10000 foobar
    100000 foobar
   1000000 foobar

$ for((i=0;i<array_size;i++));
do
    printf "%10s %10d %10s" stringarray[$i] numberarray[$i] anotherfieldarray[%i]
done

Observe que usé %10spara cadenas. %ses la parte importante. Le dice que use una cadena. El 10en el medio dice cuántas columnas serán. %des para números (dígitos).

man 1 printf para más información.

UtahJarhead
fuente
35
solo un consejo que es útil al imprimir tablas: %-10sgenerará cadenas alineadas a la izquierda de longitud 10
steffen
@UtahJarhead la referencia a las variables stringarray [$ i] debe reemplazarse por $ {stringarray [i]} y, teniendo los primeros espacios de cadena, debe estar entre comillas "$ {stringarray [i]}" para evitar que el carácter de espacio se interprete como un delimitador.
Daniel Perez
150

Usar comando de columna:

column -t -s' ' filename
PÁGINAS
fuente
Esto no funcionará para el ejemplo dado en la pregunta ya que hay espacios en la primera columna de datos.
Burhan Ali
1
@BurhanAli ¿Tengo que repetir mi comentario anterior? Todas las respuestas asumen algún delimitador. OP no ha dicho nada sobre el delimitador. Por lo tanto, el mismo delimitador también se puede usar en la columna. como hay espacios en la primera columna de datos , ¿cómo se llama como primera columna?
PP
1
No es necesario repetir. Yo los leo. Mi comentario se basa en el resultado deseado en la pregunta. Usar esta respuesta en la entrada dada no produce la salida deseada.
Burhan Ali
@BurhanAli Incluso si usa printf, necesita conocer el delimitador. %sEl especificador de formato toma espacios en blanco como delimitador. En ese caso, ninguna de las respuestas aquí funcionará. Me sorprende que hable repetidamente sobre la salida deseada cuando el análisis (usando cualquier herramienta) depende del delimitador de la entrada.
PP
2
ejemplo para preparar el delimitador:cat /etc/fstab | sed -r 's/\s+/ /g' | column -t -s' '
untore
16

Para tener exactamente la misma salida que necesita, debe formatear el archivo de esa manera:

a very long string..........\t     112232432\t     anotherfield\n
a smaller string\t      123124343\t     anotherfield\n

Y luego usando:

$ column -t -s $'\t' FILE
a very long string..........  112232432  anotherfield
a smaller string              123124343  anotherfield
Gilles Quenot
fuente
2
¿Cuál es la $de $'\t'hacer?
rjmunro
El uso de tabstops se vuelve completamente inutilizable si 2 columnas tienen más de aproximadamente 5 caracteres de tamaño diferente.
UtahJarhead
16
function printTable()
{
    local -r delimiter="${1}"
    local -r data="$(removeEmptyLines "${2}")"

    if [[ "${delimiter}" != '' && "$(isEmptyString "${data}")" = 'false' ]]
    then
        local -r numberOfLines="$(wc -l <<< "${data}")"

        if [[ "${numberOfLines}" -gt '0' ]]
        then
            local table=''
            local i=1

            for ((i = 1; i <= "${numberOfLines}"; i = i + 1))
            do
                local line=''
                line="$(sed "${i}q;d" <<< "${data}")"

                local numberOfColumns='0'
                numberOfColumns="$(awk -F "${delimiter}" '{print NF}' <<< "${line}")"

                # Add Line Delimiter

                if [[ "${i}" -eq '1' ]]
                then
                    table="${table}$(printf '%s#+' "$(repeatString '#+' "${numberOfColumns}")")"
                fi

                # Add Header Or Body

                table="${table}\n"

                local j=1

                for ((j = 1; j <= "${numberOfColumns}"; j = j + 1))
                do
                    table="${table}$(printf '#| %s' "$(cut -d "${delimiter}" -f "${j}" <<< "${line}")")"
                done

                table="${table}#|\n"

                # Add Line Delimiter

                if [[ "${i}" -eq '1' ]] || [[ "${numberOfLines}" -gt '1' && "${i}" -eq "${numberOfLines}" ]]
                then
                    table="${table}$(printf '%s#+' "$(repeatString '#+' "${numberOfColumns}")")"
                fi
            done

            if [[ "$(isEmptyString "${table}")" = 'false' ]]
            then
                echo -e "${table}" | column -s '#' -t | awk '/^\+/{gsub(" ", "-", $0)}1'
            fi
        fi
    fi
}

function removeEmptyLines()
{
    local -r content="${1}"

    echo -e "${content}" | sed '/^\s*$/d'
}

function repeatString()
{
    local -r string="${1}"
    local -r numberToRepeat="${2}"

    if [[ "${string}" != '' && "${numberToRepeat}" =~ ^[1-9][0-9]*$ ]]
    then
        local -r result="$(printf "%${numberToRepeat}s")"
        echo -e "${result// /${string}}"
    fi
}

function isEmptyString()
{
    local -r string="${1}"

    if [[ "$(trimString "${string}")" = '' ]]
    then
        echo 'true' && return 0
    fi

    echo 'false' && return 1
}

function trimString()
{
    local -r string="${1}"

    sed 's,^[[:blank:]]*,,' <<< "${string}" | sed 's,[[:blank:]]*$,,'
}

EJECUCIONES DE MUESTRA

$ cat data-1.txt
HEADER 1,HEADER 2,HEADER 3

$ printTable ',' "$(cat data-1.txt)"
+-----------+-----------+-----------+
| HEADER 1  | HEADER 2  | HEADER 3  |
+-----------+-----------+-----------+

$ cat data-2.txt
HEADER 1,HEADER 2,HEADER 3
data 1,data 2,data 3

$ printTable ',' "$(cat data-2.txt)"
+-----------+-----------+-----------+
| HEADER 1  | HEADER 2  | HEADER 3  |
+-----------+-----------+-----------+
| data 1    | data 2    | data 3    |
+-----------+-----------+-----------+

$ cat data-3.txt
HEADER 1,HEADER 2,HEADER 3
data 1,data 2,data 3
data 4,data 5,data 6

$ printTable ',' "$(cat data-3.txt)"
+-----------+-----------+-----------+
| HEADER 1  | HEADER 2  | HEADER 3  |
+-----------+-----------+-----------+
| data 1    | data 2    | data 3    |
| data 4    | data 5    | data 6    |
+-----------+-----------+-----------+

$ cat data-4.txt
HEADER
data

$ printTable ',' "$(cat data-4.txt)"
+---------+
| HEADER  |
+---------+
| data    |
+---------+

$ cat data-5.txt
HEADER

data 1

data 2

$ printTable ',' "$(cat data-5.txt)"
+---------+
| HEADER  |
+---------+
| data 1  |
| data 2  |
+---------+

REF LIB en: https://github.com/gdbtek/linux-cookbooks/blob/master/libraries/util.bash

Nam Nguyen
fuente
¡Gracias! Esto es exactamente lo que estaba buscando. Para usuarios de mac: debe eliminar el -eparámetro en los comandos de eco para que los guiones se impriman correctamente.
odm
¡Muestra impresionante y genial! ¡Gracias!
Juneyoung Oh
Cambiar los colores de salida realmente complica la alineación. No estoy seguro de por qué ... hmmm
mattdevio
@mattdevio ¿has encontrado una solución para los colores?
Carlos Florêncio
2
¡AVISO! El enlace de referencia tiene una función más actualizada.
insign
4

Es más fácil de lo que te imaginas.

Si también está trabajando con un archivo y un encabezado separados por punto y coma:

$ (head -n1 file.csv && sort file.csv | grep -v <header>) | column -s";" -t

Si está trabajando con una matriz (usando la pestaña como separador):

for((i=0;i<array_size;i++));
do

   echo stringarray[$i] $'\t' numberarray[$i] $'\t' anotherfieldarray[$i] >> tmp_file.csv

done;

cat file.csv | column -t
Renan Benedicto Pereira
fuente
4

awk solución que se ocupa de stdin

Dado columnque no es POSIX, tal vez esto sea:

mycolumn() (
  file="${1:--}"
  if [ "$file" = - ]; then
    file="$(mktemp)"
    cat > "${file}"
  fi
  awk '
  FNR == 1 { if (NR == FNR) next }
  NR == FNR {
    for (i = 1; i <= NF; i++) {
      l = length($i)
      if (w[i] < l)
        w[i] = l
    }
    next
  }
  {
    for (i = 1; i <= NF; i++)
      printf "%*s", w[i] + (i > 1 ? 1 : 0), $i
    print ""
  }
  ' "$file" "$file"
  if [ "$1" = - ]; then
    rm "$file"
  fi
)

Prueba:

printf '12 1234 1
12345678 1 123
1234 123456 123456
' > file

Comandos de prueba:

mycolumn file
mycolumn <file
mycolumn - <file

Salida para todos:

      12   1234      1
12345678      1    123
    1234 123456 123456

Ver también:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
1
El if [ "$file" = - ]; thenal final debería ser if [ "$1" = - ]; then. Con el código actual, nunca limpia sus archivos temporales.
Camusensei
3

No estoy seguro de dónde estaba ejecutando esto, pero el código que publicó no produciría el resultado que dio, al menos no en el bash con el que estoy familiarizado.

Prueba esto en su lugar:

stringarray=('test' 'some thing' 'very long long long string' 'blah')
numberarray=(1 22 7777 8888888888)
anotherfieldarray=('other' 'mixed' 456 'data')
array_size=4

for((i=0;i<array_size;i++))
do
    echo ${stringarray[$i]} $'\x1d' ${numberarray[$i]} $'\x1d' ${anotherfieldarray[$i]}
done | column -t -s$'\x1d'

Tenga en cuenta que estoy usando el carácter separador de grupo (1d) en lugar de la pestaña, porque si obtiene estas matrices de un archivo, es posible que contengan pestañas.

Benubird
fuente
0

En caso de que alguien quiera hacer eso en PHP, publiqué una esencia en Github

https://gist.github.com/redestructa/2a7691e7f3ae69ec5161220c99e2d1b3

simplemente llame:

$output = $tablePrinter->printLinesIntoArray($items, ['title', 'chilProp2']);

es posible que deba adaptar el código si está utilizando una versión de php anterior a la 7.2

después de esa llamada echo o writeLine dependiendo de su entorno.

redestructa
fuente
0

El siguiente código ha sido probado y hace exactamente lo que se solicita en la pregunta original.

Parámetros:% 30s Columna de 30 caracteres y texto alineado a la derecha. Notación entera% 10d,% 10s también funcionará. Se agregó una aclaración en los comentarios del código.

stringarray[0]="a very long string.........."
# 28Char (max length for this column)
numberarray[0]=1122324333
# 10digits (max length for this column)
anotherfield[0]="anotherfield"
# 12Char (max length for this column)
stringarray[1]="a smaller string....."
numberarray[1]=123124343
anotherfield[1]="anotherfield"

printf "%30s %10d %13s" "${stringarray[0]}" ${numberarray[0]} "${anotherfield[0]}"
printf "\n"
printf "%30s %10d %13s" "${stringarray[1]}" ${numberarray[1]} "${anotherfield[1]}"
# a var string with spaces has to be quoted
printf "\n Next line will fail \n"      
printf "%30s %10d %13s" ${stringarray[0]} ${numberarray[0]} "${anotherfield[0]}"



  a very long string.......... 1122324333  anotherfield
         a smaller string.....  123124343  anotherfield
Daniel Pérez
fuente
como señaló @steffen arriba, para alinear a la izquierda use el símbolo "-", es decir, printf "% -30s" "$ {stringarray [0]}"
Daniel Perez