คำสั่งเช่น `column -t 'ที่เก็บตัวคั่นเอาท์พุทแทน


17

ฉันกำลังแก้ไขตารางอย่างง่าย ฉันต้องการรูปแบบที่ดี ในขณะที่ฉันสามารถใช้tbl, latexหรือที่คล้ายกันนี้ดูเหมือนว่า overkill - ข้อความธรรมดาจริงๆก็เพียงพอแล้ว เพราะมันง่ายฉันก็อาจมีแหล่งที่มาเป็นเอาท์พุท ดังนั้นแหล่งที่มาควรดูดีเช่นกัน ดูเหมือนว่ามันควรจะเป็นงานที่สมบูรณ์แบบสำหรับcolumn -s '|' -t- พบตัวคั่นและแทรกช่องว่างโดยอัตโนมัติเพื่อจัดตำแหน่งตามความกว้างสูงสุดในแต่ละคอลัมน์ น่าเสียดายที่มันลบตัวคั่นดังนั้นฉันจึงไม่สามารถรันอีกครั้งหลังจากแก้ไขเพิ่มเติม มีเครื่องมือการประมวลผลข้อความที่ดีที่สามารถทำ idempotently นี้เพื่อให้เป็นผลลัพธ์หรือไม่ หรือฉันจะต้องเขียนของตัวเอง?

แก้ไข: นี่คือตัวอย่างของสิ่งที่ฉันต้องการ:

foo |   bar | baz
abc def | 12 | 23456

ควรกลายเป็น

foo     | bar | baz
abc def | 12  | 3456

เมื่อ' 'ใดที่ทั้งตัวคั่นและตัวเว้นวรรคcolumn -tทำงานได้ดี แต่รายการของฉันมีช่องว่างในนั้นฉันจึงใช้ไม่ได้ การมียานอวกาศแตกต่างจากตัวแยกทำให้สิ่งต่าง ๆ ยุ่งยาก ฉันคิดว่ามันมีประโยชน์ที่จะให้พวกมันถือเป็นตัวคั่นเมื่ออยู่ถัดจากตัวคั่น แต่นั่นไม่ใช่สิ่งที่column -s '|' -tทำ (แม้ว่าจะเห็นได้ชัดว่าพฤติกรรมปัจจุบันก็มีประโยชน์เช่นกัน)


คุณสามารถใช้ emacs org-mode ได้ การสนับสนุนตารางนั้นยอดเยี่ยมมากโดยมีสเปรดชีตเช่นฟังก์ชัน
vschum

ไม่เป็นทั่วไปเป็นสิ่งที่ผมคิดว่าจะเป็นที่เหมาะสม แต่มีโปรแกรมหลามเฉพาะสำหรับตาราง markdown ที่leancrew.com/all-this/2008/08/tables-for-markdown-and-textmate
wnoise

นี่เป็นปัญหาที่ฉันพบเจออย่างน้อยทุกสองสัปดาห์ ทางออกที่ทำงานได้เพียงวิธีเดียวในการหลีกเลี่ยงความprintfหายนะในแต่ละครั้งที่ฉันได้พบจนถึงขณะนี้คือการเพิ่มอักขระพิเศษ (เช่น@) ลงในข้อมูลและใช้งาน... | column -s@ -tภายหลัง
sjas

คำตอบ:


17

ไม่แน่ใจว่าฉันเข้าใจถูกต้องว่าปัญหาของคุณคืออะไร แต่จะสามารถแก้ไขได้โดยเพิ่มตัวคั่นชั่วคราวเพิ่มเติมหรือไม่ ด้วยเหตุนี้คุณสามารถใช้ตัวคั่นที่สองเพื่อทำเครื่องหมายการแยกทำให้ตัวคั่นเดิมไม่ถูกแตะต้อง

ดูตัวอย่างนี้ที่ฉันเพิ่ม "@" ลงในแต่ละ "|" ดังนั้นอินพุตของคำสั่งคอลัมน์จะเป็น "xxx @ | yyyy" คอลัมน์จะประมวลผล "@" ที่เก็บ "|" แตะต้อง:

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

ฉลาด. เกือบทำในสิ่งที่ฉันต้องการและทำในสิ่งที่ฉันถามจริง ๆ แล้วปล่อยให้ตัวคั่นมาฉันต้องการให้มีช่องว่างถัดจากตัวแยกที่แท้จริงเพื่อที่จะสามารถปรับลดลงได้
wnoise

@wnoise: ใช้sed 's/ *| */@| /g'แทน
Stéphane Gimenez

@ Stéphane Gimenez: และเพิ่มsed 's/ |/|/g'หลังจากcolumnแก้ไขช่องว่างพิเศษที่เพิ่มเข้ามา ตอนนี้เรามีทางออกที่ใช้ได้ดีพอสำหรับฉัน (แม้ว่ามันจะดีถ้ามันไม่ได้ขึ้นอยู่กับตัวละครพิเศษแบบนี้จะเกิดอะไรขึ้นถ้าไม่มีใครว่าง)
wnoise

3
@wnoise: แทนที่จะใช้ @ คุณสามารถใช้บางสิ่งที่โดยทั่วไปแล้วจะไม่ปรากฏเป็นข้อความเช่นค่า ASCII ที่ต่ำเช่น $ '\ x01' ... (แต่ไม่ใช่ $ '\ x00') ...
Peter.O

6

นี้ก็ไม่สามารถใช้ได้เมื่อคุณถามคำถาม แต่ณ v. 2.23 columnจากutil-linuxช่วยให้คุณเลือกคั่นเอาท์พุทผ่านทาง

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

ดังนั้นเพียงแค่เรียกใช้:

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

โปรดทราบว่าutil-linuxรุ่นนี้ไม่มีให้บริการบน Ubuntu 18.04 (และอาจเป็นเพราะ distros ที่ได้จาก Debain อื่น ๆ ) ในขณะที่เขียน มีเฉพาะbsdmainutilsรุ่นเท่านั้น bsdmainutilsรุ่นไม่สนับสนุนการส่งออกการจัดรูปแบบ
htaccess

5

นี่คือสคริปต์ทุบตี มันไม่ได้ใช้ 'column -t` และ seperator ได้รับการจัดการเหมือนกับ IFS เนื่องจากเป็น IFS (หรืออย่างน้อยที่สุด IFS รุ่นภายใน awk ของ ... ) ตัวคั่นเริ่มต้นคือ $' \ t '

สคริปต์นี้แผ่ออกจากเขตข้อมูลขวาสุดอย่างเต็มที่
'คอลัมน์' ไม่ได้ทำเช่นนี้
ด้วยการขยายคอลัมน์ทั้งหมดสคริปต์นี้สามารถ
แก้ไขได้อย่างง่ายดายเพื่อสร้างกรอบตารางเช่นกัน

บันทึก. ไฟล์อินพุตต้องถูกประมวลผลสองครั้ง
('คอลัมน์' จะต้องทำเช่นนี้)
การผ่านครั้งแรกคือการรับความกว้างสูงสุดของคอลัมน์
รอบที่สองคือการขยายสาขา (ต่อคอลัมน์)

เพิ่มตัวเลือกบางส่วนและแก้ไขข้อบกพร่องที่เห็นได้ชัด (เปลี่ยนชื่อตัวแปร :(

  • -l whitespace ตัดแต่งด้านซ้ายของฟิลด์เยื้องใด ๆ
  • -r Right whitespace กว้างกว่าข้อความที่กว้างที่สุด (สำหรับคอลัมน์)
  • -b ทั้ง -l และ -r
  • -L ตัวคั่นเอาต์พุตด้านซ้ายถูกเพิ่ม
  • -R ตัวคั่นเอาต์พุตขวาถูกเพิ่ม
  • -B ทั้ง -L และ -R
  • -S เลือกตัวคั่นเอาต์พุต

#!/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

ทำได้ดีมาก แน่นอนฉันหวังว่าจะมีบางสิ่งที่ไม่จำเป็นต้องมีการเขียนโปรแกรมใหม่
wnoise


1

นี่คือการบิดสองรอบในคำตอบของhmontoliuซึ่งหลีกเลี่ยงการต้องเขียนโค้ดตัวคั่นยากโดยคาดเดาจากข้อมูลอินพุต

  1. $dการป้อนข้อมูลแยกตัวละครที่ไม่ใช่ตัวเลขเดียวล้อมรอบด้วยพื้นที่จัดเรียงพวกเขาโดยการซึ่งเป็นเรื่องธรรมดามากที่สุดและถือว่าตัวอักษรที่พบมากที่สุดคือตัวคั่นซึ่งได้รับมอบหมายให้
  2. ดำเนินการต่อไปมากขึ้นหรือน้อยลงตามคำตอบของhmonoliuแต่ใช้ ASCII NULLเป็นช่องว่างภายในแทนที่จะเติมแทน@ตามความคิดเห็นของPeterO

รหัสเป็นฟังก์ชั่นที่รับชื่อไฟล์หรือข้อมูลอื่นจาก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 ;
}

ผลลัพธ์ของalgn foo(หรือยังalgn < foo):

foo      | bar  | baz
abc def  | 12   | 23456

ดูที่นี่อีกหนึ่งปีต่อมาดูเหมือนว่าการร้องขอSTDINไม่สามารถทำได้และไม่ควรใช้งานเพราะใช้STDINขึ้นสองครั้ง การทดสอบกับไฟล์ขนาดใหญ่ (ประมาณ 80 ล้านบรรทัด) แสดงว่าทำงานได้อย่างถูกต้อง อืม ...
agc

0

ใช้แนวคิดของhmontoliuเพื่อใช้คำสั่งง่าย ๆ :

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

หมายเหตุ:

  • ${1:-,}- เป็นอาร์กิวเมนต์แรกที่มี,ค่าเริ่มต้น
  • sedแทรกแรกเป็นสัญลักษณ์กลาง ( $intermอาร์กิวเมนต์ที่ 2 หรือ~โดยค่าเริ่มต้น)
  • จากนั้นcolumnแทนที่สัญลักษณ์กลางด้วยช่องว่างที่ทำการจัดตำแหน่ง
  • ครั้งที่สองsedล้างช่องว่างที่ซ้ำซ้อนหลังจากcolumnคำสั่ง

ตัวอย่างการใช้งาน:

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

a   : bb: cccc
aaaa: b : cc

นอกจากนี้ยังเป็นเรื่องดีที่ idempotent: คุณสามารถนำไปใช้ได้หลายครั้งและรับผลลัพธ์เดียวกัน (ตัวอย่างเช่นเมื่อคุณแก้ไขเป็นกลุ่มและจัดตำแหน่ง)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.