Як я можу вирівняти стовпці таблиць у Bash?


86

Я хотів би вивести текст у форматі таблиці. Що я намагався зробити, це повторити елементи масиву з '\ t', але він був зміщений.

Мій код

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

Мій результат

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

Бажаний вихід

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

Відповіді:


95

printf чудово, але люди про це забувають.

$ 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

Зверніть увагу, що я використовував %10sдля струн. %sє важливою частиною. Це говорить йому використовувати рядок. 10В середині каже , скільки стовпців вона повинна бути. %dпризначений для цифр (цифр).

man 1 printf для отримання додаткової інформації.


35
лише одна порада, яка корисна під час друку таблиць: %-10swiil генерує рядки, вирівняні за лівим краєм, довжиною 10
steffen

@UtahJarhead посилання на змінні stringarray [$ i] слід замінити на $ {stringarray [i]} і, маючи пробіли в першому рядку, його потрібно вказати в лапки "$ {stringarray [i]}", щоб уникнути інтерпретації пробілу як роздільник.
Даніель Перес

150

Використовуйте команду стовпця:

column -t -s' ' filename

Це не буде працювати для прикладу, наведеного у питанні, оскільки в першому стовпці даних є пробіли.
Бурхан Алі

1
@BurhanAli Чи повинен я повторити свій попередній коментар? Усі відповіді передбачають деякий роздільник. OP не сказав про роздільник. Тож той самий роздільник можна використовувати і в стовпці. оскільки в першому стовпці даних є пробіли, як тоді ви називаєте це першим стовпцем?
PP

1
Не потрібно повторювати. Я їх читав. Мій коментар базується на бажаних результатах у питанні. Використання цієї відповіді на даному вході не дає бажаного результату.
Бурхан Алі

@BurhanAli Навіть якщо ви використовуєте printf, вам потрібно знати роздільник. %sспецифікатор формату приймає пробіли як роздільник. У цьому випадку жодна з відповідей тут не спрацює. Я вражений тим, що ви неодноразово говорите про бажаний результат, коли синтаксичний аналіз (за допомогою будь-якого інструменту) залежить від роздільника вводу.
PP

2
приклад для підготовки роздільника:cat /etc/fstab | sed -r 's/\s+/ /g' | column -t -s' '
зняти запис

16

Щоб отримати точно такий самий результат, як вам потрібно, потрібно відформатувати файл так:

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

А потім використовуючи:

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

2
Що $в $'\t'робити?
rjmunro

Використання вкладки стає абсолютно непридатним, якщо 2 стовпці мають розмір більше приблизно 5 символів.
ЮтаДжархед,


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

ЗРАЗКИ РОБІТ

$ 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 за адресою: https://github.com/gdbtek/linux-cookbooks/blob/master/libraries/util.bash


Дякую! це саме те, що я шукав. Для користувачів Mac: вам потрібно видалити -eпараметр у командах echo, щоб тире були правильно надруковані.
ODM

Чудовий і чудовий зразок! Дякую!
Juneyoung Oh,

Зміна вихідних кольорів насправді заплутує з вирівнюванням. Не знаю, чому ... хммм
mattdevio

@mattdevio ти знайшов виправлення кольорів?
Карлос Флоренсіо,

2
ГОЛОВА! Посилання ref має більш оновлену функцію.
знак

4

Це простіше, ніж вам цікаво.

Якщо ви працюєте з відокремленим файлом із комою та заголовком теж:

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

Якщо ви працюєте з масивом (використовуючи вкладку як роздільник):

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 рішення, яке має справу зі stdin

Оскільки columnце не POSIX, можливо, це:

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
)

Тест:

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

Тестові команди:

mycolumn file
mycolumn <file
mycolumn - <file

Результат для всіх:

      12   1234      1
12345678      1    123
    1234 123456 123456

Дивитися також:


1
В if [ "$file" = - ]; thenкінці має бути if [ "$1" = - ]; then. За допомогою поточного коду ви ніколи не очищаєте тимчасові файли.
Камузенсей

3

Не впевнений, де ви запускали це, але код, який ви розмістили, не видасть результату, який ви дали, принаймні не в баші, який я знайомий.

Спробуйте замість цього:

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'

Зверніть увагу, що я використовую груповий розділовий символ (1d) для вкладки, оскільки якщо ви отримуєте ці масиви з файлу, вони можуть містити вкладки.


0

Про всяк випадок, якщо хтось захоче це зробити у PHP, я опублікував суть на Github

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

просто зателефонуйте:

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

можливо, вам знадобиться адаптувати код, якщо ви використовуєте версію php, старшу за 7.2

після цього зателефонуйте echo або writeLine залежно від вашого оточення.


0

Нижче код протестовано і робить саме те, що вимагається в оригінальному питанні.

Параметри:% 30s Стовпець із 30 символів та вирівнювання за правою частиною тексту. % 10d цілочисельних позначень,% 10s також буде працювати. Додано роз’яснення, включене до коментарів коду.

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

як вказав @steffen вище, для вирівнювання за лівим краєм використовуйте символ "-" тобто printf "% -30s" "$ {stringarray [0]}"
Даніель Перес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.