Làm cách nào để căn chỉnh các cột của bảng trong Bash?


84

Tôi muốn xuất văn bản định dạng bảng. Những gì tôi đã cố gắng làm là lặp lại các phần tử của một mảng với '\ t', nhưng nó đã bị lệch.

Mã của tôi

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

Đầu ra của tôi

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

Kết quả mong muốn

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

Câu trả lời:


95

printf là tuyệt vời, nhưng mọi người quên nó đi.

$ 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

Lưu ý rằng tôi đã sử dụng %10scho chuỗi. %slà phần quan trọng. Nó yêu cầu nó sử dụng một chuỗi. Ở 10giữa cho biết nó có bao nhiêu cột. %dlà cho số (chữ số).

man 1 printf để biết thêm thông tin.


35
chỉ là một lời khuyên đó là hữu ích khi in bảng: %-10swiil tạo ra chuỗi canh trái chiều dài 10
Steffen

@UtahJarhead tham chiếu đến các biến stringarray [$ i] phải được thay thế bằng $ {stringarray [i]} và có dấu cách chuỗi nắm tay, nó phải được trích dẫn "$ {stringarray [i]}" để tránh biểu đồ không gian được hiểu là một dấu phân cách.
Daniel Perez

149

Sử dụng lệnh cột:

column -t -s' ' filename

Điều này sẽ không hoạt động đối với ví dụ được đưa ra trong câu hỏi vì có khoảng trắng trong cột dữ liệu đầu tiên.
Burhan Ali

1
@BurhanAli Tôi có phải lặp lại nhận xét trước đây của mình không? Tất cả các câu trả lời giả sử một số dấu phân cách. OP chưa nói về dấu phân cách. Vì vậy, cùng một dấu phân cách cũng có thể được sử dụng trong cột. vì có khoảng trắng trong cột dữ liệu đầu tiên thì làm thế nào để bạn gọi nó là cột đầu tiên ?
PP

1
Không cần lặp lại. Tôi đọc chúng. Nhận xét của tôi dựa trên kết quả mong muốn trong câu hỏi. Sử dụng câu trả lời này cho đầu vào đã cho không tạo ra đầu ra mong muốn.
Burhan Ali

@BurhanAli Ngay cả khi bạn sử dụng printf, bạn cũng cần biết dấu phân cách. %sđịnh dạng định dạng lấy khoảng trắng làm dấu phân cách. Trong trường hợp đó, không có câu trả lời nào ở đây sẽ hoạt động. Tôi ngạc nhiên rằng bạn liên tục nói về đầu ra mong muốn khi việc phân tích cú pháp (sử dụng bất kỳ công cụ nào ) phụ thuộc vào dấu phân cách của đầu vào.
PP

2
ví dụ để chuẩn bị dấu phân cách:cat /etc/fstab | sed -r 's/\s+/ /g' | column -t -s' '
chưa đến

16

Để có đầu ra chính xác như bạn cần, bạn cần phải định dạng tệp như sau:

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

Và sau đó sử dụng:

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

2
Là gì $trong $'\t'làm gì?
rjmunro

Việc sử dụng các tab sẽ hoàn toàn không sử dụng được nếu 2 cột có kích thước khác nhau hơn 5 ký tự.
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:]]*$,,'
}

CHẠY MẪU

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


Cảm ơn! điều này thật đúng với gì mà tôi đã tìm kiếm. Đối với người dùng mac: bạn phải xóa -etham số trong các lệnh echo để in các dấu gạch ngang một cách chính xác.
odm

Mẫu tuyệt vời và tuyệt vời! Cảm ơn!
Juneyoung Oh

Thay đổi màu sắc đầu ra thực sự gây rối với việc căn chỉnh. Bạn không chắc chắn lý do tại sao ... hmmm
mattdevio

@mattdevio bạn đã tìm thấy bản sửa lỗi cho màu sắc chưa?
Carlos Florêncio

2
ĐỨNG LÊN! Liên kết giới thiệu có một chức năng cập nhật hơn.
insign

4

Nó dễ dàng hơn bạn tự hỏi.

Nếu bạn đang làm việc với cả tệp và tiêu đề được phân tách bằng dấu chấm phẩy:

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

Nếu bạn đang làm việc với mảng (sử dụng tab làm dấu phân tách):

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 giải pháp giao dịch với stdin

columnkhông phải là POSIX, có thể đây là:

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
)

Kiểm tra:

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

Các lệnh kiểm tra:

mycolumn file
mycolumn <file
mycolumn - <file

Đầu ra cho tất cả:

      12   1234      1
12345678      1    123
    1234 123456 123456

Xem thêm:


1
Các if [ "$file" = - ]; thencuối cùng nên if [ "$1" = - ]; then. Với mã hiện tại, bạn không bao giờ xóa các tệp tạm thời của mình.
Camusensei

3

Không chắc bạn đã chạy cái này ở đâu, nhưng mã bạn đã đăng sẽ không tạo ra kết quả mà bạn đã đưa ra, ít nhất là không có trong bash mà tôi quen thuộc.

Hãy thử cái này thay thế:

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'

Lưu ý rằng tôi đang sử dụng ký tự phân tách nhóm (1d) ở đầu tab, bởi vì nếu bạn đang lấy các mảng này từ một tệp, chúng có thể chứa các tab.


0

Đề phòng ai đó muốn làm điều đó bằng PHP, tôi đã đăng ý chính trên Github

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

chỉ cần gọi:

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

bạn có thể cần phải điều chỉnh mã nếu bạn đang sử dụng phiên bản php cũ hơn 7.2

sau đó gọi echo hoặc writeLine tùy theo môi trường của bạn.


0

Mã dưới đây đã được thử nghiệm và thực hiện chính xác những gì được yêu cầu trong câu hỏi ban đầu.

Tham số:% 30s Cột 30 ký tự và văn bản căn phải. Ký hiệu số nguyên% 10d,% 10s cũng sẽ hoạt động. Đã thêm làm rõ được bao gồm trên các bình luận mã.

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

như được chỉ bởi @steffen ở trên, để sử dụng căn trái "-" biểu tượng tức là printf "% -30s" "$ {stringarray [0]}"
Daniel Perez
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.