Lệnh như `cột -t` thay vào đó giữ các dấu phân cách ở đầu ra


17

Tôi đang chỉnh sửa một bảng đơn giản. Tôi muốn có nó được định dạng độc đáo. Trong khi tôi có thể sử dụng tbl, latexhoặc tương tự, điều này có vẻ quá mức - văn bản đơn giản thực sự là đủ. Vì nó đơn giản nên tôi cũng có thể có nguồn là đầu ra. Vì vậy, nguồn nên nhìn tốt quá. Điều này có vẻ như là một công việc hoàn hảo cho column -s '|' -t- nó tìm các dấu phân cách và tự động chèn các khoảng trắng để căn chỉnh theo chiều rộng tối đa trong mỗi cột. Thật không may, nó xóa các dấu phân cách, vì vậy tôi không thể chạy lại nó sau khi chỉnh sửa thêm. Có công cụ xử lý văn bản tốt nào có thể thực hiện việc này một cách bình thường, để đầu ra của nó đóng vai trò là đầu vào không? Hay tôi cần phải tự viết?

EDIT: đây là một ví dụ về những gì tôi muốn:

foo |   bar | baz
abc def | 12 | 23456

nên trở thành

foo     | bar | baz
abc def | 12  | 3456

Khi ' 'cả hai dải phân cách và miếng đệm, column -thoạt động độc đáo. Nhưng các mặt hàng của tôi có không gian trong đó, vì vậy tôi không thể sử dụng nó. Có các miếng đệm khác biệt với các dải phân cách làm phức tạp mọi thứ. Tôi nghĩ thật hữu ích khi chúng được coi là các ký tự phân cách khi bên cạnh dấu phân cách, nhưng đó không phải là điều gì column -s '|' -txảy ra (mặc dù rõ ràng hành vi hiện tại cũng hữu ích).


Bạn có thể sử dụng chế độ org của emacs. Hỗ trợ bảng thực sự khá tuyệt vời, cung cấp bảng tính như chức năng.
vschum

Không chung chung như những gì tôi nghĩ sẽ hợp lý, nhưng có một chương trình python dành riêng cho các bảng đánh dấu tại leancrew.com/all-this/2008/08/tables-for-markdown-and-textmate .
wnoise

Đây là một vấn đề tôi gặp phải ít nhất hai tuần một lần. Giải pháp khả thi duy nhất để vượt qua printfholocaust mỗi lần, mà tôi đã tìm thấy cho đến nay, là thêm một char (thích @) duy nhất vào dữ liệu và sử dụng ... | column -s@ -tsau đó.
sjas

Câu trả lời:


17

Không chắc chắn nếu tôi hiểu đúng vấn đề của bạn là gì. Nhưng, nó có thể được giải quyết bằng cách thêm một dấu phân cách thời gian bổ sung? do đó bạn có thể sử dụng dấu tách thứ hai để đánh dấu các dấu tách, giữ cho dấu tách gốc không bị ảnh hưởng.

Xem ví dụ này nơi tôi thêm "@" vào mỗi "|" vì vậy, đầu vào của lệnh cột sẽ là "xxx @ | yyyy". Cột sẽ xử lý "@" giữ "|" không bị ảnh hưởng:

~$ echo "foo | this is some text | bar" | sed 's/|/@|/g'  | column -s '@' -t
foo   | this is some text   | bar

Tài giỏi. Gần như làm những gì tôi muốn, và thực tế là làm những gì tôi yêu cầu - để lại các dải phân cách. Tôi cũng muốn các khoảng trống bên cạnh các dải phân cách thực sự có thể được điều chỉnh xuống, thay vì chỉ lên, như ở đây.
wnoise

@wnoise: sử dụng sed 's/ *| */@| /g'thay thế
Stéphane Gimenez

@ Stéphane Gimenez: Và thêm sed 's/ |/|/g'sau khi columnsửa các khoảng trắng thêm vào. Bây giờ chúng tôi có một giải pháp hoạt động đủ tốt cho tôi. (Mặc dù thật tuyệt nếu nó không phụ thuộc vào một nhân vật phụ như thế này. Điều gì sẽ xảy ra nếu một người không có mặt?)
wnoise

3
@wnoise: Thay vì @, bạn có thể sử dụng thứ gì đó thường không xuất hiện trong văn bản, như giá trị ASCII thấp, ví dụ: $ '\ x01' ... (nhưng không phải $ '\ x00') ...
Peter.O

6

Điều này không khả dụng khi bạn đặt câu hỏi nhưng kể từ câu 2.23 column từ util-linuxcho phép bạn chọn dấu tách đầu ra thông qua

   -o, --output-separator string
          Specify the columns delimiter for table output (default is two spaces).

Vì vậy, chỉ cần chạy:

 column -s '|' -o '|' -t infile

Lưu ý rằng util-linuxphiên bản không có sẵn trên Ubuntu 18.04 (và có thể là các bản phát hành Debain khác) tại thời điểm viết. Chỉ có bsdmainutilsphiên bản có sẵn. Các bsdmainutilsphiên bản không hỗ trợ đầu ra định dạng.
htaccess

5

Đây là một kịch bản bash. Nó không sử dụng 'cột -t` và bộ tách biệt được xử lý chính xác như IFS, vì đó là IFS (hoặc ít nhất, phiên bản nội bộ của IFS của awk) ... Dấu phân cách mặc định là $' \ t '

Kịch bản này hoàn toàn đệm ra trường ngoài cùng bên phải.
'cột' không làm điều này.
Bằng cách đệm tất cả các cột, tập lệnh này có thể
dễ dàng sửa đổi để tạo khung bảng.

Ghi chú. Tệp đầu vào cần được xử lý hai lần
('cột' cũng cần thực hiện việc này)
Vượt qua đầu tiên là lấy chiều rộng tối đa của cột.
Vượt qua thứ hai là để mở rộng các trường (trên mỗi cột)

Đã thêm một số tùy chọn và sửa lỗi phát sáng (đổi tên biến :(

  • -l Khoảng trắng bên trái của bất kỳ trường thụt lề nào
  • -r Cắt khoảng trắng bên phải rộng hơn văn bản rộng nhất (đối với cột)
  • -b Cả -l và -r
  • -L Dấu phân tách đầu ra bên trái được thêm vào
  • -R Dấu phân cách đầu ra phải được thêm vào
  • -B Cả -L và -R
  • -S Chọn bộ tách đầu ra

#!/bin/bash
#
#   script [-F sep] [file]
#
#   If file is not specified, stdin is read 
#    
# ARGS ######################################################################
l=;r=;L=;R=;O=;F=' ' # defaults
for ((i=1;i<=${#@};i++)) ;do
  case "$1" in
    -- ) shift 1;((i--));break ;;
    -l ) l="-l";shift 1;((i-=1)) ;;        #  left strip whitespace
    -r ) r="-r";shift 1;((i-=1)) ;;        # right strip whitespace
    -b ) l="-l";r="-r";shift 1;((i-=1)) ;; # strip  both -l and -r whitespace
    -L ) L="-L";shift 1;((i-=1)) ;;        #  Left output delimiter is added
    -R ) R="-R";shift 1;((i-=1)) ;;        # Right output delimiter is added
    -B ) L="-L";R="-R";shift 1;((i-=1)) ;; # output Both -L and -R delimiters
    -F ) F="$2";shift 2;((i-=2)) ;; # source separator
    -O ) O="$2";shift 2;((i-=2)) ;; # output  separator. Default = 1st char of -F 
    -* ) echo "ERROR: invalid option: $1" 1>&2; exit 1 ;;
     * ) break ;;
  esac
done
#
if  [[ -z "$1" ]] ;then # no filename, so read stdin
  f="$(mktemp)"
  ifs="$IFS"; IFS=$'\n'; set -f # Disable pathname expansion (globbing)
  while read -r line; do
    printf "%s\n" "$line" >>"$f"
  done
  IFS="$ifs"; set +f # re-enable pathname expansion (globbing)
else
  f="$1"
fi
[[ -f "$f" ]] || { echo "ERROR: Input file NOT found:" ;echo "$f" ;exit 2 ; }
[[ -z "$F" ]] && F=' '        # input Field Separator string
[[ -z "$O" ]] && O="$F"       # output Field Separator
                 O="${O:0:1}" #   use  single char only

# MAIN ######################################################################
max="$( # get max length of each field/column, and output them
  awk -vl="$l" -vr="$r" -vL="$L" -vR="$R" -vF="$F" -vO="$O" '
    BEGIN { if (F!="") FS=F }
    { for (i=1;i<=NF;i++) { 
        if (l=="-l") { sub("^[ \t]*","",$i) }
        if (r=="-r") { sub("[ \t]*$","",$i) }
        len=length($i); if (len>max[i]) { max[i]=len } 
        if (i>imax) { imax=i } 
      } 
    }
    END { for(i=1;i<=imax;i++) { printf("%s ",max[i]) } }
  ' "$f" 
)"

awk -vl="$l" -vr="$r" -vL="$L" -vR="$R" -vF="$F" -vO="$O" -v_max="$max" '
  BEGIN { if (F!="") FS=F; cols=split(_max,max," ") }
  { # Bring each field up to max len and output with delimiter
    printf("%s",L=="-L"?O:"")
    for(i=1;i<=cols;i++) { if (l=="-l") { sub("^[ \t]*","",$i) } 
                           if (r=="-r") { sub("[ \t]*$","",$i) }
      printf("%s%"(max[i]-length($i))"s%s",$i,"",i==cols?"":O) 
    } 
    printf("%s\n",R=="-R"?O:"")
  }
' "$f"

# END #######################################################################    
if  [[ -z "$1" ]] ;then # no filename, so stdin was used
  rm "$f"   # delete temp file
fi
exit

Hoàn thành tốt Tất nhiên, tôi đã hy vọng điều gì đó sẽ không thực sự yêu cầu viết một chương trình mới.
wnoise


1

Đây là một điều chỉnh hai bước đối với câu trả lời của hmontoliu , điều này tránh việc cần phải cứng mã phân định, bằng cách đoán nó từ dữ liệu đầu vào.

  1. đầu vào phân tích cú pháp cho các ký tự không chữ và số được bao quanh bởi khoảng trắng, sắp xếp chúng theo cách phổ biến nhất và giả sử ký tự phổ biến nhất là dấu phân cách, được gán cho $d.
  2. tiến hành nhiều hơn hoặc ít hơn như trong câu trả lời của hmonoliu , nhưng sử dụng NULL ASCII làm phần đệm, thay vì @, theo nhận xét của PeterO .

Mã này là một hàm chấp nhận tên tệp hoặc đầu vào khác từ STDIN :

algn() { 
    d="$(grep -ow '[^[:alnum:]]' "${1:-/dev/stdin}"  | \
         sort | uniq -c | sort -rn | sed -n '1s/.*\(.$\)/\1/p')" ;
    sed "s/ *$d */\x01$d /g" "${1:-/dev/stdin}"  | column -s $'\001' -t ;
}

Đầu ra của algn foo(hoặc cũng algn < foo):

foo      | bar  | baz
abc def  | 12   | 23456

Nhìn vào điều này một năm sau, có vẻ như lệnh gọi STDIN không thể và không nên hoạt động vì nó sử dụng hết STDIN hai lần. Thử nghiệm với các tệp lớn (khoảng 80 triệu dòng) cho thấy rõ ràng nó hoạt động chính xác. Hmm ...
agc

0

Sử dụng ý tưởng của hmontoliu để thực hiện lệnh đơn giản:

#! /bin/bash
delim="${1:-,}"
interm="${2:-\~}"
sed "s/$delim/$interm$delim/g" | column -t -s "$interm" | sed "s/  $delim/$delim/g"

Bình luận:

  • ${1:-,}- là đối số đầu tiên với ,mặc định
  • cái đầu tiên sedchèn một ký hiệu trung gian ( $intermđối số thứ 2 hoặc ~theo mặc định)
  • sau đó columnthay thế biểu tượng trung gian bằng các khoảng trắng làm căn chỉnh
  • thứ hai seddọn sạch các không gian dư thừa sau columnlệnh

Ví dụ sử dụng:

$ echo "
a: bb: cccc
aaaa: b : cc
" | align :

a   : bb: cccc
aaaa: b : cc

Điều đó cũng tốt ở chỗ nó bình thường: bạn có thể áp dụng nó nhiều lần và nhận được kết quả tương tự (ví dụ: khi bạn chỉnh sửa trong vim và realign).

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.