จัดเรียงคอลัมน์ใหม่โดยใช้การตัด


135

ฉันมีไฟล์ในรูปแบบต่อไปนี้

คอลัมน์ 1 คอลัมน์ 2
str1 1
str2 2
str3 3

ฉันต้องการจัดเรียงคอลัมน์ใหม่ ฉันลองคำสั่งด้านล่าง

ตัด -f2,1 file.txt

คำสั่งไม่เรียงลำดับคอลัมน์ใหม่ มีความคิดว่าทำไมจึงไม่ทำงาน?

ขอบคุณ.

คำตอบ:


148

สำหรับcut(1)หน้าคน:

ใช้หนึ่งและใช้เพียงหนึ่งใน -b, -c หรือ -f แต่ละ LIST ประกอบด้วยช่วงเดียวหรือหลายช่วงคั่นด้วยลูกน้ำ ข้อมูลที่เลือกจะถูกเขียนในลำดับเดียวกับที่อ่านและเขียนเพียงครั้งเดียว

ถึงฟิลด์ 1 ก่อนเพื่อที่จะพิมพ์ตามด้วยฟิลด์ 2

ใช้awkแทน:

awk '{ print $2 " " $1}' file.txt

12
แย่เกินไปที่cutไม่รองรับคำสั่งสั่งซื้อใหม่ที่ใช้งานง่ายนี้ อย่างไรก็ตามเคล็ดลับอื่น ๆ : คุณสามารถใช้awk' -FSและ-OFSตัวเลือกเพื่อใช้ตัวคั่นฟิลด์อินพุตและเอาต์พุตที่กำหนดเอง (เช่น-dและ--output-delimiterสำหรับcut)
malana

12
ขออภัยFSเป็นตัวเลือกOFSเป็นตัวแปร เช่นawk -v OFS=";" -F"\t" '{print $2,$1}'
malana

2
หมายเหตุสำหรับผู้ใช้ Windows ของ Git Bash: หากคุณมีผลลัพธ์แปลก ๆ จากคำสั่งด้านบนซึ่งดูเหมือนว่าคอลัมน์จะทับกันและกันการส่งคืนแคร่จะเป็นการตำหนิ เปลี่ยน EOL ในไฟล์ของคุณจาก CRLF เป็น LF
jakub.g

1
หรือหากคุณไม่ต้องการเปลี่ยนไฟล์อินพุตคุณสามารถ| sed 's/\r//' | awk
ไพพ์

2
หนึ่งนี้เป็นง่ายมาก แต่อาจจะมีประโยชน์สำหรับบางเพียงแทนที่พื้นที่ที่มี \ t สำหรับการจัดเรียงใหม่ด้วยแท็บและในกรณีที่คุณต้องการคอลัมน์เพิ่มเติมคุณสามารถทำมันเป็นเช่นawk '{print $4 "\t" $2 "\t" $6 "\t" $7}' file
FatihSarigol

64

คุณสามารถรวมcutและpaste:

paste <(cut -f2 file.txt) <(cut -f1 file.txt)

ผ่านความคิดเห็น: เป็นไปได้ที่จะหลีกเลี่ยงการทุบตีและลบตัวอย่างหนึ่งของการตัดออกโดยทำ:

paste file.txt file.txt | cut -f2,3

3
ไม่แน่ใจว่าสิ่งนี้เข้าข่าย "ฉลาด" หรือไม่ แต่: f = file.txt วาง <(cut -f2 $ f) <(cut -f1 $ f) นอกจากนี้ฉันทราบว่าวิธีนี้เป็นวิธีที่ง่ายที่สุดเมื่อคุณมีคอลัมน์จำนวนมากและต้องการย้ายไปรอบ ๆ บล็อกขนาดใหญ่
Michael Rusch

ใช้ไม่ได้กับเซลล์ที่มีความยาวตัวแปรในคอลัมน์เดียวกัน
kraymer

2
@kraymer คุณหมายถึงอะไร? cutทำงานได้ดีสำหรับคอลัมน์ที่มีความยาวผันแปรได้ตราบเท่าที่คุณมีตัวคั่นคอลัมน์ที่ไม่ซ้ำกัน
tripleee

1
หากต้องการกำจัดไฟล์ที่ซ้ำซ้อนคุณอาจใช้ที:
JJW5432

2
เป็นไปได้ที่จะหลีกเลี่ยงbashismsและลบหนึ่งอินสแตนซ์cutโดยทำ: paste file.txt file.txt | cut -f2,3
agc

7

ใช้เพียงเปลือก

while read -r col1 col2
do
  echo $col2 $col1
done <"file"

ซึ่งมักจะไม่มีประสิทธิภาพ โดยทั่วไปคุณจะพบว่าสคริปต์ Awk ที่เกี่ยวข้องนั้นเร็วกว่ามาก นอกจากนี้คุณควรระมัดระวังในการอ้างอิงค่า"$col2"และ"$col1"- อาจมีตัวอักษรเชลล์หรือตัวอักษรอื่น ๆ ในข้อมูล
tripleee

7

คุณสามารถใช้ Perl เพื่อ:

perl -ane 'print "$F[1] $F[0]\n"' < file.txt
  • -e ตัวเลือกหมายถึงดำเนินการคำสั่งหลังจากนั้น
  • -n หมายถึงอ่านทีละบรรทัด (เปิดไฟล์ในกรณีนี้คือ STDOUT และวนซ้ำบรรทัด)
  • -a หมายถึงการแบ่งเส้นดังกล่าวเป็นเวกเตอร์ที่เรียกว่า @F ("F" - เหมือนฟิลด์) Perl ทำดัชนีเวกเตอร์โดยเริ่มจาก 0 ซึ่งแตกต่างจากการตัดซึ่งทำดัชนีฟิลด์เริ่มต้นรูปแบบ 1
  • คุณสามารถเพิ่มรูปแบบ -F (โดยไม่มีช่องว่างระหว่าง -F และรูปแบบ ) เพื่อใช้รูปแบบเป็นตัวคั่นฟิลด์เมื่ออ่านไฟล์แทนช่องว่างเริ่มต้น

ข้อดีของการเรียกใช้ perl คือ (ถ้าคุณรู้จัก Perl) คุณสามารถคำนวณ F ได้มากกว่าการจัดเรียงคอลัมน์ใหม่


perlrun (1) อ้างว่า -a กำหนดโดยปริยาย -n แต่ถ้าฉันรันโดยไม่ตั้งค่า -n ดูเหมือนจะไม่วนซ้ำ แปลก
เทรนตัน

รุ่นอะไร? perl -ae printทำงานcatให้ฉัน
pwes

5

ใช้join:

join -t $'\t' -o 1.2,1.1 file.txt file.txt

หมายเหตุ:

  • -t $'\t'ในGNU joinใช้งานง่ายมากขึ้น-t '\t' โดยไม่เกิดความ$ล้มเหลว ( coreutils v8.28และรุ่นก่อนหน้า?); อาจเป็นข้อผิดพลาดที่$ควรมีวิธีแก้ปัญหาเช่นนี้ ดู: ยูนิกซ์เข้าร่วมคั่นถ่าน

  • joinต้องการชื่อไฟล์สองชื่อแม้ว่าจะมีไฟล์เดียวที่ใช้งานได้ การใช้ชื่อเดียวกันสองครั้งจะjoinทำให้เกิดการกระทำที่ต้องการ

  • สำหรับระบบที่มีทรัพยากรน้อยจะjoinมีขนาดเล็กกว่าเครื่องมือบางตัวที่ใช้ในคำตอบอื่น ๆ :

    wc -c $(realpath `which cut join sed awk perl`) | head -n -1
      43224 /usr/bin/cut
      47320 /usr/bin/join
     109840 /bin/sed
     658072 /usr/bin/gawk
    2093624 /usr/bin/perl
    

3

เพิ่งทำงานเกี่ยวกับสิ่งที่คล้ายกันมากฉันไม่ใช่ผู้เชี่ยวชาญ แต่ฉันคิดว่าจะแบ่งปันคำสั่งที่ฉันใช้ ฉันมี csv แบบหลายคอลัมน์ซึ่งฉันต้องการเพียง 4 คอลัมน์จากนั้นฉันต้องเรียงลำดับใหม่

ไฟล์ของฉันคือไพพ์ '|' คั่น แต่สามารถสลับออกได้

LC_ALL=C cut -d$'|' -f1,2,3,8,10 ./file/location.txt | sed -E "s/(.*)\|(.*)\|(.*)\|(.*)\|(.*)/\3\|\5\|\1\|\2\|\4/" > ./newcsv.csv

เป็นที่ยอมรับว่ามันหยาบและพร้อมมาก แต่ก็สามารถปรับแต่งให้เหมาะสมได้!


สิ่งนี้ไม่ตอบคำถามที่วางไว้ ด้วยความตั้งใจของสแต็กโอเวอร์โปรดระบุเวลาในการตอบปัญหาก่อนโพสต์
Bill Gale

0

ใช้ sed

ใช้ sed กับนิพจน์ย่อยที่ซ้อนกันของนิพจน์ทั่วไปพื้นฐานเพื่อจับภาพและจัดลำดับเนื้อหาคอลัมน์ใหม่ วิธีนี้เหมาะที่สุดเมื่อมีการตัดจำนวน จำกัด เพื่อจัดเรียงคอลัมน์ใหม่เช่นในกรณีนี้

แนวคิดพื้นฐานคือการล้อมรอบส่วนที่น่าสนใจของรูปแบบการค้นหาด้วย\(และ\)ซึ่งสามารถเล่นได้ในรูปแบบการแทนที่โดย\#ที่#แสดงตำแหน่งตามลำดับของนิพจน์ย่อยในรูปแบบการค้นหา

ตัวอย่างเช่น:

$ echo "foo bar" | sed "s/\(foo\) \(bar\)/\2 \1/"

อัตราผลตอบแทน:

bar foo

ข้อความภายนอกนิพจน์ย่อยจะถูกสแกน แต่ไม่ถูกเก็บไว้สำหรับการเล่นในสตริงแทนที่

แม้ว่าคำถามจะไม่ได้กล่าวถึงคอลัมน์ความกว้างคงที่ แต่เราจะพูดถึงที่นี่เนื่องจากเป็นการวัดที่คุ้มค่าสำหรับการแก้ปัญหาใด ๆ ที่วางไว้ เพื่อความเรียบง่ายสมมติว่าไฟล์มีการคั่นด้วยช่องว่างแม้ว่าโซลูชันสามารถขยายสำหรับตัวคั่นอื่น ๆ ได้

การยุบช่องว่าง

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

ไฟล์:

bash-3.2$ cat f
Column1    Column2
str1       1
str2       2
str3       3
bash-3.2$ od -a f
0000000    C   o   l   u   m   n   1  sp  sp  sp  sp   C   o   l   u   m
0000020    n   2  nl   s   t   r   1  sp  sp  sp  sp  sp  sp  sp   1  nl
0000040    s   t   r   2  sp  sp  sp  sp  sp  sp  sp   2  nl   s   t   r
0000060    3  sp  sp  sp  sp  sp  sp  sp   3  nl 
0000072

แปลง:

bash-3.2$ sed "s/\([^ ]*\)[ ]*\([^ ]*\)[ ]*/\2 \1/" f
Column2 Column1
1 str1
2 str2
3 str3
bash-3.2$ sed "s/\([^ ]*\)[ ]*\([^ ]*\)[ ]*/\2 \1/" f | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  nl
0000020    1  sp   s   t   r   1  nl   2  sp   s   t   r   2  nl   3  sp
0000040    s   t   r   3  nl
0000045

การรักษาความกว้างของคอลัมน์

ตอนนี้เรามาขยายวิธีการไปยังไฟล์ที่มีคอลัมน์ความกว้างคงที่ในขณะที่อนุญาตให้คอลัมน์มีความกว้างต่างกัน

ไฟล์:

bash-3.2$ cat f2
Column1    Column2
str1       1
str2       2
str3       3
bash-3.2$ od -a f2
0000000    C   o   l   u   m   n   1  sp  sp  sp  sp   C   o   l   u   m
0000020    n   2  nl   s   t   r   1  sp  sp  sp  sp  sp  sp  sp   1  sp
0000040   sp  sp  sp  sp  sp  nl   s   t   r   2  sp  sp  sp  sp  sp  sp
0000060   sp   2  sp  sp  sp  sp  sp  sp  nl   s   t   r   3  sp  sp  sp
0000100   sp  sp  sp  sp   3  sp  sp  sp  sp  sp  sp  nl
0000114

แปลง:

bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f2
Column2 Column1
1       str1      
2       str2      
3       str3      
bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f2 | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  sp
0000020   sp  sp  nl   1  sp  sp  sp  sp  sp  sp  sp   s   t   r   1  sp
0000040   sp  sp  sp  sp  sp  nl   2  sp  sp  sp  sp  sp  sp  sp   s   t
0000060    r   2  sp  sp  sp  sp  sp  sp  nl   3  sp  sp  sp  sp  sp  sp
0000100   sp   s   t   r   3  sp  sp  sp  sp  sp  sp  nl 
0000114

สุดท้ายนี้แม้ว่าตัวอย่างของคำถามจะไม่มีสตริงที่มีความยาวไม่เท่ากัน แต่นิพจน์ sed นี้ก็สนับสนุนกรณีนี้

ไฟล์:

bash-3.2$ cat f3
Column1    Column2
str1       1      
string2    2      
str3       3      

แปลง:

bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f3
Column2 Column1   
1       str1      
2       string2   
3       str3    
bash-3.2$ sed "s/\([^ ]*\)\([ ]*\) \([^ ]*\)\([ ]*\)/\3\4 \1\2/" f3 | od -a
0000000    C   o   l   u   m   n   2  sp   C   o   l   u   m   n   1  sp
0000020   sp  sp  nl   1  sp  sp  sp  sp  sp  sp  sp   s   t   r   1  sp
0000040   sp  sp  sp  sp  sp  nl   2  sp  sp  sp  sp  sp  sp  sp   s   t
0000060    r   i   n   g   2  sp  sp  sp  nl   3  sp  sp  sp  sp  sp  sp
0000100   sp   s   t   r   3  sp  sp  sp  sp  sp  sp  nl 
0000114

เปรียบเทียบกับวิธีการจัดเรียงคอลัมน์ใหม่ภายใต้เชลล์

  • น่าแปลกสำหรับเครื่องมือจัดการไฟล์ awk ไม่เหมาะอย่างยิ่งสำหรับการตัดจากฟิลด์ไปยังจุดสิ้นสุดของเร็กคอร์ด ใน sed นี้สามารถทำได้โดยใช้การแสดงออกปกติเช่น\(xxx.*$\)ที่xxxเป็นการแสดงออกเพื่อให้ตรงกับคอลัมน์

  • การใช้วางและตัด subshells จะยุ่งยากเมื่อใช้งานภายในเชลล์สคริปต์ โค้ดที่ทำงานจากบรรทัดคำสั่งไม่สามารถแยกวิเคราะห์เมื่อนำมาไว้ในเชลล์สคริปต์ อย่างน้อยนี่ก็เป็นประสบการณ์ของฉัน (ซึ่งผลักดันให้ฉันเข้าหาแนวทางนี้)


0

ขยายคำตอบจาก @Met โดยใช้ Perl:
หากอินพุตและเอาต์พุตเป็นแบบคั่นด้วย TAB:

perl -F'\t' -lane 'print join "\t", @F[1, 0]' in_file

หากอินพุตและเอาต์พุตคั่นด้วยช่องว่าง:

perl -lane 'print join " ", @F[1, 0]' in_file

ที่นี่
-eบอกให้ Perl มองหาโค้ดแบบอินไลน์แทนที่จะอยู่ในไฟล์สคริปต์แยกต่างหาก
-nอ่านอินพุตทีละ 1 บรรทัด
-lลบตัวคั่นบันทึกอินพุต ( \nบน * NIX) หลังจากอ่านบรรทัด (คล้ายกับchomp) และเพิ่มเอาต์พุต บันทึกคั่น ( \nบน * NIX) แต่ละprint,
-aแยกสายสัญญาณเข้าบนช่องว่างลงในอาร์เรย์@F,
-F'\t'ร่วมกับแยกสายการป้อนข้อมูลบนแท็บแทนช่องว่างลงในอาร์เรย์-a@F

@F[1, 0]คืออาร์เรย์ที่ประกอบด้วยองค์ประกอบที่ 2 และ 1 ของอาร์เรย์@Fตามลำดับนี้ โปรดจำไว้ว่าอาร์เรย์ใน Perl มีการจัดทำดัชนีเป็นศูนย์ในขณะที่ช่องในcutมีการจัดทำดัชนี 1 รายการ ดังนั้นในเขตนี้เป็นเขตเดียวกับคนใน@F[0, 1]cut -f1,2

โปรดทราบว่าสัญกรณ์ดังกล่าวช่วยให้สามารถจัดการอินพุตได้อย่างยืดหยุ่นมากกว่าคำตอบอื่น ๆ ที่โพสต์ไว้ด้านบน (ซึ่งเหมาะสำหรับงานง่ายๆ) ตัวอย่างเช่น:

# reverses the order of fields:
perl -F'\t' -lane 'print join "\t", reverse @F' in_file

# prints last and first fields only:
perl -F'\t' -lane 'print join "\t", @F[-1, 0]' in_file
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.