¿Existe un comando simple para generar columnas delimitadas por tabulaciones?

67

Por ejemplo, tengo un archivo (producido con echo -e "var1\tvar2\t\var3\tvar4" > foo) que se genera como:

$ cat foo
case    elems   meshing nlsys
uniform 2350    0.076662        2.78
non-conformal   348     0.013332        0.55
scale   318     0.013333        0.44
smarter 504     0.016666        0.64
submodel        360     .009999 0.40
unstruct-quad   640     0.019999        0.80
unstruct-tri    1484    0.01    0.88

Prefiero la salida como esta (aquí usé vimy :set tabstop=14):

case          elems         meshing       nlsys
uniform       2350          0.076662      2.78
non-conformal 348           0.013332      0.55
scale         318           0.013333      0.44
smarter       504           0.016666      0.64
submodel      360           .009999       0.40
unstruct-quad 640           0.019999      0.80
unstruct-tri  1484          0.01          0.88

Puedo obtener la misma funcionalidad catsi la uso $ tabs=15en bash (vea esta pregunta ). ¿Hay algún programa que realice este tipo de formateo automáticamente? No quiero experimentar con el tabsvalor antes de catcrear un archivo.

Sebastian
fuente

Respuestas:

87

Usualmente uso el columnprograma para esto, está en un paquete llamado bsdmainutilsen Debian:

column -t foo

Salida:

case           elems  meshing   nlsys
uniform        2350   0.076662  2.78
non-conformal  348    0.013332  0.55
scale          318    0.013333  0.44
smarter        504    0.016666  0.64
submodel       360    .009999   0.40
unstruct-quad  640    0.019999  0.80
unstruct-tri   1484   0.01      0.88

Extracto de column(1)mi sistema:

...

-t      Determine the number of columns the input contains and create a
        table.  Columns are delimited with whitespace, by default, or
        with the characters supplied using the -s option.  Useful for
        pretty-printing displays.

...
Thor
fuente
¡Excelente! ¡muchas gracias! Ya estaba instalado en mi máquina.
Sebastian
11
es posible que desee agregar -s $'\t'(aunque no se encuentra en todas las implementaciones de columna) en caso de que algunos de los campos contengan espacios.
Stéphane Chazelas
2
@RakholiyaJenish $'\t'significa carácter de tabulación. Nueva línea es $'\n'y así sucesivamente.
Manwe
2
Usé esto como column -ts: /etc/passwd. ¡Se ve bien!
kyb
1
@kyb: se ve aún mejor con -n, es decir, evitar fusionar múltiples delimitadores adyacentes
Thor
10

Varias opciones:

var1=uniform var2=2350 var3=0.076662 var4=2.78

printf '%-15s %-10s %-12s %s\n' \
  case elems messing nlsys \
  "$var1" "$var2" "$var3" "$var4"

printf '%s\t%s\t%s\t%s\n' \
  case elems messing nlsys \
  "$var1" "$var2" "$var3" "$var4" |
  expand -t 15,25,37

printf '%s\t%s\t%s\t%s\n' \
  case elems messing nlsys \
  "$var1" "$var2" "$var3" "$var4" |
  column -t -s $'\t'

La columna es un comando no estándar, algunas implementaciones / versiones no admiten la opción -s. Calcula el ancho de la columna en función de la entrada, pero eso significa que solo puede comenzar a mostrarse una vez que se haya alimentado toda la entrada. $'...'La sintaxis ksh93 también se encuentra en zsh y bash.

Con zsh:

values=(
  case elems messing nlsys
  "$var1" "$var2" "$var3" "$var4"
)
print -arC4 -- "$values[@]"
Stéphane Chazelas
fuente
4

También puede usar rscomo alternativa a column -t:

(x=$(cat);rs -c -z $(wc -l<<<"$x")<<<"$x")

-ccambia el separador de columna de entrada, pero -csolo establece el separador de columna de entrada en una pestaña. -zestablece el ancho de cada columna al ancho de la entrada más larga de la columna en lugar de hacer que todas las columnas tengan el mismo ancho. Si algunas líneas tienen menos columnas que la primera línea, agregue -n.

nisetama
fuente
Que rses eso No tengo ese comando instalado en mi CentOS ni en mis sistemas Ubuntu / Mint.
Anthon
1
@Anthon Es un comando BSD que también viene con OS X, llamado así por la función de reforma en APL. El nombre del paquete Debian es justo rs, por lo que puede instalarlo con apt-get install rs.
nisetama
¿podría dar un ejemplo de cómo se llamaría el comando (x=$(cat);rs -c -z $(wc -l<<<"$x")<<<"$x")? No sé cómo usaría eso con un archivo csv
baxx
3

Otra herramienta que puede hacer esto es tsv-prettyde TSV Utilities de eBay (descargo de responsabilidad: soy el autor). Toma el paso adicional de alinear campos numéricos en el punto decimal. Por ejemplo:

$ tsv-pretty foo
case           elems   meshing  nlsys
uniform         2350  0.076662   2.78
non-conformal    348  0.013332   0.55
scale            318  0.013333   0.44
smarter          504  0.016666   0.64
submodel         360   .009999   0.40
unstruct-quad    640  0.019999   0.80
unstruct-tri    1484  0.01       0.88

Hay varias opciones de formato. Por ejemplo, -usubraya el encabezado y -fformatea los flotantes en un campo de manera similar para facilitar la lectura:

$ tsv-pretty foo -f -u
case           elems   meshing  nlsys
----           -----   -------  -----
uniform         2350  0.076662   2.78
non-conformal    348  0.013332   0.55
scale            318  0.013333   0.44
smarter          504  0.016666   0.64
submodel         360  0.009999   0.40
unstruct-quad    640  0.019999   0.80
unstruct-tri    1484  0.010000   0.88

Más información está disponible en la referencia tsv-pretty .

JonDeg
fuente
Esto es realmente útil
Arefe
1

La pregunta era sobre la salida de columnas delimitadas por tabuladores.

Entonces, la respuesta correcta es una pequeña adaptación de la respuesta de @nisetama. Agregué la opción -C $ '\ t' que establece el formato de salida.

x=$(cat foo2); rs -C$'\t' $(wc -l <<<"$x") <<<"$x"

Kudo a @nisetama aunque :)

thehpi
fuente
1
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:]]*$,,'
}

EJEMPLOS DE EJECUCIÓN

$ 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
interesante solución de solo bash - gracias por compartir
Sebastian
Esto es demasiado complicado. Y no es solo bash, ya que hay comandos externos como sedser utilizados.
codeforester