Wie kann ich die Tabellenspalten in Bash ausrichten?


80

Ich möchte einen Text im Tabellenformat ausgeben. Ich habe versucht, die Elemente eines Arrays mit '\ t' wiederzugeben, aber es war falsch ausgerichtet.

Mein Code

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

Meine Ausgabe

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

Gewünschte Ausgabe

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

Antworten:


92

printf ist toll, aber die Leute vergessen es.

$ 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

Beachten Sie, dass ich %10sfür Zeichenfolgen verwendet habe. %sist der wichtige Teil. Es fordert es auf, eine Zeichenfolge zu verwenden. Das 10in der Mitte sagt, wie viele Spalten es sein soll. %dsteht für Zahlen (Ziffern).

man 1 printf Für mehr Information.


35
Nur ein Ratschlag, der beim Drucken von Tabellen nützlich ist: %-10sWir erzeugen linksbündige Zeichenfolgen mit einer Länge von 10
steffen

@UtahJarhead Der Verweis auf die Variablen stringarray [$ i] sollte durch $ {stringarray [i]} ersetzt werden. Bei den ersten Zeichenfolgen muss "$ {stringarray [i]}" angegeben werden, um zu vermeiden, dass Leerzeichen als a interpretiert werden Trennzeichen.
Daniel Perez

144

Verwenden Sie den Spaltenbefehl:

column -t -s' ' filename

Dies funktioniert für das in der Frage angegebene Beispiel nicht, da in der ersten Datenspalte Leerzeichen vorhanden sind.
Burhan Ali

1
@BurhanAli Muss ich meinen vorherigen Kommentar wiederholen? Alle Antworten setzen ein Trennzeichen voraus. OP hat nichts über das Trennzeichen gesagt. Das gleiche Trennzeichen kann also auch in der Spalte verwendet werden. Da die erste Datenspalte Leerzeichen enthält, wie nennt man sie dann als erste Spalte?
PP

1
Keine Notwendigkeit zu wiederholen. Ich lese sie. Mein Kommentar basiert auf der gewünschten Ausgabe in der Frage. Die Verwendung dieser Antwort für die angegebene Eingabe erzeugt nicht die gewünschte Ausgabe.
Burhan Ali

@BurhanAli Auch wenn Sie printf verwenden, müssen Sie das Trennzeichen kennen. %sDer Formatbezeichner verwendet Leerzeichen als Trennzeichen. In diesem Fall funktioniert keine der Antworten hier. Ich bin erstaunt, dass Sie wiederholt über die gewünschte Ausgabe sprechen, wenn das Parsen (mit einem beliebigen Werkzeug) vom Trennzeichen der Eingabe abhängt.
PP

2
Beispiel für die Vorbereitung des Begrenzers:cat /etc/fstab | sed -r 's/\s+/ /g' | column -t -s' '
am

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

MUSTER LÄUFT

$ 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 unter: https://github.com/gdbtek/linux-cookbooks/blob/master/libraries/util.bash


Vielen Dank! Genau das habe ich gesucht. Für Mac-Benutzer: Sie müssen den -eParameter in den Echo-Befehlen entfernen , damit die Striche korrekt gedruckt werden.
Odm

Super und tolle Probe! Vielen Dank!
Juneyoung Oh

Das Ändern der Ausgabefarben beeinträchtigt die Ausrichtung erheblich. Ich weiß nicht warum ... hmmm
mattdevio

@mattdevio hast du eine Lösung für die Farben gefunden?
Carlos Florêncio

1
KOPF HOCH! Der Ref-Link hat eine aktuellere Funktion.
Insign

15

Um genau die gleiche Ausgabe zu erhalten, die Sie benötigen, müssen Sie die Datei folgendermaßen formatieren:

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

Und dann mit:

$ column -t -s $'\t' FILE
a very long string..........  112232432  anotherfield
a smaller string              123124343  anotherfield

2
Was ist das $in $'\t'gerade?
rjmunro

Die Verwendung von Tabstops wird völlig unbrauchbar, wenn 2 Spalten mehr als 5 Zeichen unterschiedlicher Größe haben.
UtahJarhead


4

Es ist einfacher als Sie sich fragen.

Wenn Sie auch mit einer durch Semikolon getrennten Datei und einem Header arbeiten:

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

Wenn Sie mit einem Array arbeiten (Tabulator als Trennzeichen verwenden):

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

4

awk Lösung, die sich mit stdin befasst

Da columnes sich nicht um POSIX handelt, ist dies möglicherweise:

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
)

Prüfung:

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

Testbefehle:

mycolumn file
mycolumn <file
mycolumn - <file

Ausgabe für alle:

      12   1234      1
12345678      1    123
    1234 123456 123456

Siehe auch:


1
Das if [ "$file" = - ]; thenam Ende sollte sein if [ "$1" = - ]; then. Mit dem aktuellen Code bereinigen Sie niemals Ihre temporären Dateien.
Camusensei

3

Ich bin mir nicht sicher, wo Sie dies ausgeführt haben, aber der von Ihnen veröffentlichte Code würde nicht die von Ihnen angegebene Ausgabe erzeugen, zumindest nicht in der Bash, mit der ich vertraut bin.

Versuchen Sie stattdessen Folgendes:

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'

Beachten Sie, dass ich das Gruppentrennzeichen (1d) anstelle der Registerkarte verwende, da diese möglicherweise Arrays enthalten, wenn Sie diese Arrays aus einer Datei abrufen.


0

Nur für den Fall, dass jemand das in PHP machen möchte, habe ich einen Kern auf Github gepostet

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

Rufen Sie einfach an:

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

Möglicherweise müssen Sie den Code anpassen, wenn Sie eine PHP-Version verwenden, die älter als 7.2 ist

Rufen Sie danach je nach Umgebung echo oder writeLine auf.


0

Der folgende Code wurde getestet und macht genau das, was in der ursprünglichen Frage verlangt wird.

Parameter:% 30s Spalte mit 30 Zeichen und Text rechtsbündig. % 10d Ganzzahlnotation,% 10s funktioniert auch. Klarstellung in Codekommentaren hinzugefügt.

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

Wie oben von @steffen angegeben, verwenden Sie für die Linksausrichtung das Symbol "-", dh printf "% -30s" "$ {stringarray [0]}"
Daniel Perez,
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.