Perintah seperti `kolom -t` yang menggantikan pemisah tetap dalam output

17

Saya sedang mengedit tabel sederhana. Saya ingin diformat dengan baik. Sementara saya bisa menggunakan tbl,, latexatau serupa, ini tampaknya berlebihan - teks biasa benar-benar cukup. Karena sederhana, saya mungkin juga memiliki sumber menjadi output. Jadi sumbernya juga harus terlihat bagus. Ini sepertinya merupakan pekerjaan yang sempurna untuk column -s '|' -t- ia menemukan separator dan secara otomatis memasukkan spasi untuk menyelaraskan sesuai dengan lebar maksimum di setiap kolom. Sayangnya, ini menghapus pemisah, jadi saya tidak bisa memutarnya lagi setelah diedit lebih lanjut. Apakah ada alat pemrosesan teks yang bagus yang dapat melakukan ini dengan idempoten, sehingga hasilnya berfungsi sebagai input? Atau apakah saya perlu menulis sendiri?

EDIT: inilah contoh yang saya inginkan:

foo |   bar | baz
abc def | 12 | 23456

harus menjadi

foo     | bar | baz
abc def | 12  | 3456

Kapan ' 'pemisah dan pengatur jarak, column -tbekerja dengan baik. Tapi item saya memiliki ruang di dalamnya, jadi saya tidak bisa menggunakannya. Memiliki spacer berbeda dari separator menyulitkan banyak hal. Saya pikir itu berguna untuk mereka diperlakukan sebagai karakter pemisah ketika di sebelah pemisah, tapi bukan itu yang column -s '|' -tterjadi (meskipun jelas perilaku saat ini juga berguna).

Wnoise
sumber
Anda dapat menggunakan emacs org-mode. Dukungan tabel sebenarnya cukup luar biasa, menyediakan fungsionalitas seperti spreadsheet.
vschum
Tidak umum seperti yang saya pikir masuk akal, tetapi ada program python khusus untuk tabel penurunan harga di leancrew.com/all-this/2008/08/tables-for-markdown-and-textmate .
Berkumandang
Ini adalah masalah yang saya temui setidaknya setiap dua minggu. Satu-satunya solusi yang layak untuk mem-bypass printfholocaust setiap kali, yang telah saya temukan sejauh ini, adalah menambahkan karakter unik (seperti @) ke dalam data, dan menggunakannya ... | column -s@ -tsetelahnya.
sjas

Jawaban:

17

Tidak yakin apakah saya mengerti benar apa masalah Anda. Tetapi, dapatkah itu diselesaikan dengan menambahkan pemisah temporal tambahan? karenanya Anda dapat menggunakan pemisah kedua untuk menandai pemisahan, menjaga pemisah asli tidak tersentuh.

Lihat contoh ini di mana saya menambahkan "@" ke masing-masing "|" jadi input dari perintah kolom adalah "xxx @ | yyyy". Kolom akan memproses "@" menjaga "|" tak tersentuh:

~$ echo "foo | this is some text | bar" | sed 's/|/@|/g'  | column -s '@' -t
foo   | this is some text   | bar
hmontoliu
sumber
Pintar. Hampir melakukan apa yang saya inginkan, dan benar-benar melakukan apa yang saya minta - meninggalkan pemisah. Saya juga ingin ruang di sebelah pemisah yang sebenarnya dapat disesuaikan ke bawah, bukan hanya ke atas, seperti di sini.
Berkumandang
@wnoise: gunakan sed 's/ *| */@| /g'saja
Stéphane Gimenez
@ Stéphane Gimenez: Dan menambahkan sed 's/ |/|/g'setelah columnperbaikan, ruang ekstra ditambahkan. Kami sekarang memiliki solusi yang cukup baik untuk saya. (Meskipun akan lebih baik jika tidak bergantung pada karakter tambahan seperti ini. Bagaimana jika seseorang tidak tersedia?)
Wnoise
3
@wnoise: Alih-alih @, Anda dapat menggunakan sesuatu yang biasanya tidak muncul dalam teks, seperti nilai ASCII yang rendah, misalnya. $ '\ x01' ... (tetapi bukan $ '\ x00') ...
Peter.O
6

Ini tidak tersedia ketika Anda mengajukan pertanyaan tetapi pada v. 2.23 column dari util-linuxmemungkinkan Anda untuk memilih pemisah output melalui

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

Jadi cukup jalankan:

 column -s '|' -o '|' -t infile
don_crissti
sumber
Perhatikan bahwa util-linuxversi ini tidak tersedia di Ubuntu 18.04 (dan mungkin distro lain yang berasal dari Debain) pada saat penulisan. Hanya bsdmainutilsversi yang tersedia. The bsdmainutilsVersi tidak mendukung format output.
htaccess
5

Ini skrip bash. Itu tidak menggunakan 'kolom -t`, dan pemisah ditangani persis seperti IFS, karena itu adalah IFS (atau setidaknya, versi internal awk dari IFS) ... Pembatas default adalah $' \ t '

Script ini sepenuhnya mencentang bidang paling kanan.
'kolom' tidak melakukan ini.
Dengan mengisi semua kolom, skrip ini dapat
dengan mudah dimodifikasi untuk membuat bingkai tabel juga.

Catatan. File input perlu diproses dua kali
('kolom' juga perlu melakukan ini)
Lulus pertama adalah untuk mendapatkan lebar maks kolom.
Lulus kedua adalah untuk memperluas bidang (per kolom)

Menambahkan beberapa opsi dan memperbaiki bug mencolok (mengganti nama variabel :(

  • -l Kiri spasi putih dari setiap bidang yang berlekuk
  • -r Kanan memangkas spasi lebih luas dari teks terluas (untuk kolom)
  • -b Keduanya -l dan -r
  • -L Pembatas output kiri ditambahkan
  • -R pembatas output kanan ditambahkan
  • -B Baik -L dan -R
  • -S Pilih output seperator

#!/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
Peter.O
sumber
Bagus sekali. Tentu saja, saya berharap sesuatu yang sebenarnya tidak memerlukan penulisan program baru.
Berkumandang
2

Lihatlah plugin vim yang disebut Tabularize

:Tabularize /<delim>
Amos Folarin
sumber
1

Ini adalah tweak dua-pass pada jawaban hmontoliu , yang menghindari perlunya pembatas kode yang sulit, dengan menebaknya dari data input.

  1. masukan parse untuk karakter non-alfanumerik tunggal yang dikelilingi oleh spasi, urutkan berdasarkan yang paling umum, dan anggap karakter paling umum adalah pembatas, yang ditugaskan untuk $d.
  2. lanjutkan kurang lebih seperti pada jawaban hmonoliu , tetapi gunakan ASCII NULL sebagai bantalan, alih-alih @, sesuai dengan komentar PeterO .

Kode adalah fungsi yang menerima nama file, atau masukan dari 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 ;
}

Output dari algn foo(atau juga algn < foo):

foo      | bar  | baz
abc def  | 12   | 23456
agc
sumber
Melihat ini setahun kemudian, sepertinya permohonan STDIN tidak bisa dan tidak seharusnya berhasil karena ia menghabiskan dua kali STDIN . Pengujian dengan file besar (sekitar 80 juta baris) menunjukkan bahwa ternyata berfungsi dengan benar. Hmm ...
AGC
0

Ide hmontoliu yang digunakan untuk mengimplementasikan perintah sederhana:

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

Komentar:

  • ${1:-,}- adalah argumen pertama dengan ,sebagai default
  • yang pertama sedmenyisipkan simbol perantara ( $intermargumen 2 atau ~secara default)
  • lalu columnganti simbol perantara dengan spasi yang melakukan perataan
  • yang kedua sedmembersihkan ruang redundan setelah columnperintah

Contoh penggunaan:

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

a   : bb: cccc
aaaa: b : cc

Ini juga bagus karena idempoten: Anda dapat menerapkannya beberapa kali dan mendapatkan hasil yang sama (misalnya ketika Anda mengedit dalam vim dan meluruskan kembali).

Alexey
sumber