วิธีค้นหาฟิลด์สุดท้ายโดยใช้ 'ตัด'


310

โดยไม่ต้องใช้sedหรือawk, เพียง cutวิธีการที่ฉันจะได้รับข้อมูลที่ผ่านมาเมื่อจำนวนของฟิลด์เป็นที่รู้จักหรือมีการเปลี่ยนแปลงทุกบรรทัด?


8
คุณรักcutคำสั่ง :) หรือไม่ ทำไมไม่สั่ง Linux อื่น ๆ
Jayesh Bhoi

7
โดยไม่ต้องsedหรือawk: perl -pe 's/^.+\s+([^\s]+)$/$1/'.
จอร์แดน


4
@MestreLion หลายครั้งที่ผู้คนอ่านคำถามเพื่อค้นหาวิธีการแก้ไขปัญหาที่หลากหลาย อันนี้เริ่มต้นด้วยหลักฐานเท็จที่cutสนับสนุนบางอย่างที่มันไม่ได้ แต่ฉันคิดว่ามันมีประโยชน์เพราะมันบังคับให้ผู้อ่านพิจารณาโค้ดที่ง่ายต่อการติดตาม ผมอยากรวดเร็ววิธีที่ง่ายต่อการใช้งานcutโดยไม่จำเป็นต้องใช้ไวยากรณ์หลายawk, grep, sedฯลฯrevสิ่งที่ไม่หลอกลวง; สง่างามมากและบางสิ่งที่ฉันไม่เคยพิจารณา (แม้ว่าจะมีสถานการณ์ที่น่ารำคาญสำหรับสถานการณ์อื่น ๆ ) ฉันชอบอ่านวิธีการอื่นจากคำตอบอื่น ๆ
Beejor

3
มาที่นี่เป็นปัญหาชีวิตจริง: ฉันต้องการค้นหานามสกุลไฟล์ที่แตกต่างกันทั้งหมดในต้นไม้ต้นกำเนิดเพื่ออัปเดตไฟล์. gitattributes ด้วย ดังนั้นfind | cut -d. -f<last>ความโน้มเอียงทางธรรมชาติคือ
studog

คำตอบ:


679

คุณสามารถลองสิ่งนี้:

echo 'maps.google.com' | rev | cut -d'.' -f 1 | rev

คำอธิบาย

  • rev ย้อนกลับ "maps.google.com" เป็น moc.elgoog.spam
  • cut ใช้จุด (เช่น '.') เป็นตัวคั่นและเลือกเขตข้อมูลแรกซึ่งก็คือ moc
  • สุดท้ายเรากลับรายการอีกครั้งเพื่อรับ com

6
มันไม่ได้ใช้อย่างเดียวcutแต่มันไม่มีsedหรือawkคิดว่า OP คิดอย่างไร?
Jayesh Bhoi

7
@tom OP ได้ถามคำถามมากกว่านี้ในไม่กี่ชั่วโมงที่ผ่านมา จากการโต้ตอบของเรากับ OP เรารู้ว่า awk / sed / etc ไม่ได้รับอนุญาตในการบ้านของเขา แต่ยังไม่มีการอ้างอิงถึง rev ดังนั้นมันจึงคุ้มค่ากับการยิง
zedfoxus

4
@zfus ฉันเห็น อาจต้องการติดอีกครั้งในrevภายหลัง
ทอม

17
คู่revที่ยอดเยี่ยม!
ฟอร์ดกัว

6
ยอดเยี่ยมเรียบง่ายสมบูรณ์แบบขอบคุณสำหรับคำอธิบายเช่นกัน - มีคนไม่มากพอที่จะอธิบายแต่ละขั้นตอนด้วยคำสั่งที่ยาวตามท่อ
Pete

128

ใช้การขยายพารามิเตอร์ สิ่งนี้มีประสิทธิภาพมากกว่าคำสั่งภายนอกชนิดใด ๆcut(หรือgrep) รวมอยู่ด้วย

data=foo,bar,baz,qux
last=${data##*,}

ดูBashFAQ # 100สำหรับข้อมูลเบื้องต้นเกี่ยวกับการจัดการสตริงดั้งเดิมใน bash


3
@ErwinWessels: เพราะ bash ช้ามาก ๆ ใช้ bash เพื่อเรียกใช้ไปป์ไลน์เพื่อไม่ประมวลผลข้อมูลเป็นกลุ่ม ฉันหมายความว่านี่เป็นสิ่งที่ดีถ้าคุณมีข้อความหนึ่งบรรทัดอยู่ในตัวแปรเชลล์หรือถ้าคุณต้องการที่while IFS= read -ra array_var; do :;done <(cmd)จะประมวลผลสองสามบรรทัด แต่สำหรับไฟล์ขนาดใหญ่ rev | cut | rev น่าจะเร็วกว่า! (และแน่นอนว่า awk จะเร็วกว่านั้น)
Peter Cordes

2
@PeterCordes, awk จะเร็วขึ้นสำหรับไฟล์ขนาดใหญ่แน่นอน แต่ต้องใช้การป้อนข้อมูลที่เป็นธรรมเพื่อเอาชนะต้นทุนการเริ่มต้นที่คงที่ (นอกจากนี้ยังมีเชลล์เช่น ksh93 - ที่มีประสิทธิภาพใกล้เคียงกับ awk ซึ่งไวยากรณ์ที่ให้ไว้ในคำตอบนี้ยังคงใช้ได้; bash ค่อนข้างเฉื่อยชา แต่ไม่ใกล้เคียงกับตัวเลือกเดียวเท่านั้น)
Charles Duffy

1
ขอบคุณ @PeterCordes; ตามปกติฉันเดาว่าแต่ละเครื่องมือมีกรณีการใช้งาน
Erwin Wessels

1
นี่เป็นวิธีที่เร็วที่สุดและรัดกุมที่สุดในการลดขนาดตัวแปรภายในbashสคริปต์ (สมมติว่าคุณใช้bashสคริปต์อยู่แล้ว) ไม่ต้องโทรหาอะไรภายนอก
Ken Sharp

1
@Balmipour ... แต่rev เป็นระบบปฏิบัติการเฉพาะที่คุณใช้ซึ่งไม่ได้มาตรฐานสำหรับทุกระบบยูนิกซ์ ดูรายการบทสำหรับส่วน POSIX เกี่ยวกับคำสั่งและยูทิลิตี้ - มันไม่มีอยู่ และ${var##prefix_pattern}เป็นไม่ได้ในความเป็นจริงทุบตีเฉพาะ; มันอยู่ในมาตรฐาน POSIX shให้ดูที่ส่วนท้ายของ 2.6.2 (เชื่อมโยง) ดังนั้นrevมันจึงมีอยู่ในเชลล์ที่เข้ากันได้เสมอ
Charles Duffy

89

cutมันเป็นไปไม่ได้โดยใช้เพียง นี่คือวิธีใช้grep:

grep -o '[^,]*$'

แทนที่เครื่องหมายจุลภาคสำหรับตัวคั่นอื่น


3
หากต้องการทำตรงข้ามและค้นหาทุกสิ่งยกเว้นฟิลด์สุดท้ายให้ทำ:grep -o '^.*,'
Ariel

2
สิ่งนี้มีประโยชน์อย่างยิ่งเพราะrevเพิ่มปัญหายูนิโค้ดอักขระหลายไบต์ในกรณีของฉัน
Brice

3
ฉันพยายามทำสิ่งนี้บน MinGW แต่เวอร์ชัน grep ของฉันไม่รองรับ -o ดังนั้นฉันจึงใช้sed 's/^.*,//'ซึ่งแทนที่อักขระทั้งหมดจนถึงและรวมเครื่องหมายจุลภาคสุดท้ายด้วยสตริงว่าง
TamaMcGlinn

46

โดยไม่ต้อง awk? ... แต่มันง่ายมากกับ awk:

echo 'maps.google.com' | awk -F. '{print $NF}'

AWK เป็นเครื่องมือที่มีประสิทธิภาพยิ่งกว่าที่จะมีในกระเป๋าของคุณ -F ถ้าสำหรับตัวคั่นฟิลด์ NF คือจำนวนฟิลด์ (หมายถึงดัชนีสุดท้าย)


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

3
ใช้หลีกเลี่ยงการยกเลิกที่จำเป็นของการที่อาจชะลอตัวลงสคริปต์สำหรับไฟล์การใช้งานยาวนานecho awk -F. '{print $NF}' <<< 'maps.google.com'
Anil_M

14

มีหลายวิธี คุณสามารถใช้สิ่งนี้ได้เช่นกัน

echo "Your string here"| tr ' ' '\n' | tail -n1
> here

เห็นได้ชัดว่าควรป้อนพื้นที่ว่างสำหรับคำสั่ง tr ด้วยตัวคั่นที่คุณต้องการ


ขอบคุณ! บางสิ่งบางอย่างที่ทำงานในการดวลจุดโทษ busybox 1.0.0 :)
kevinf

1
รู้สึกเหมือนคำตอบที่ง่ายที่สุดสำหรับฉันลดท่อและความหมายที่ชัดเจนขึ้น
joeButler

1
ที่จะไม่ทำงานสำหรับไฟล์ทั้งหมดซึ่งเป็นสิ่งที่ OP อาจหมายถึง
อาเมียร์

7

นี่เป็นทางออกเดียวที่เป็นไปได้สำหรับการใช้อะไร แต่ตัด

echo "string" | ตัด -d '.' -f2- [repeat_following_part_forever_or_until_out_of_memory:] | ตัด -d '.' -f2-

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

ใช่วิธีแก้ปัญหาที่โง่มาก แต่เป็นวิธีเดียวที่ตรงกับเกณฑ์ที่ฉันคิด


2
ดี ใช้ 'สุดท้าย' ออกจาก "สตริง" และใช้งานได้
แมตต์

2
ฉันรักเมื่อทุกคนพูดว่าบางสิ่งเป็นไปไม่ได้แล้วมีใครบางคนตีระฆังด้วยคำตอบที่ทำงาน แม้ว่ามันจะโง่จริง ๆ
Beejor

เราสามารถวนซ้ำcut -f2-ในลูปจนกว่าเอาต์พุตจะไม่เปลี่ยนแปลงอีกต่อไป
loa_in_

4

หากสตริงอินพุตของคุณไม่มีเครื่องหมายสแลชคุณสามารถใช้basenameและ subshell:

$ basename "$(echo 'maps.google.com' | tr '.' '/')"

นี้ไม่ได้ใช้sedหรือawkแต่มันก็ยังไม่ได้ใช้cutอย่างใดอย่างหนึ่งเพื่อให้ฉันไม่ได้ค่อนข้างแน่ใจว่ามันมีคุณสมบัติเป็นคำตอบสำหรับคำถามเป็นคำพูดของตน

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

$ basename "$(echo 'maps.google.com/some/url/things' | tr '/' '|' | tr '.' '/')" | tr '|' '/'

2

การปฏิบัติตามคำแนะนำของเพื่อน

#!/bin/bash
rcut(){

  nu="$( echo $1 | cut -d"$DELIM" -f 2-  )"
  if [ "$nu" != "$1" ]
  then
    rcut "$nu"
  else
    echo "$nu"
  fi
}

$ export DELIM=.
$ rcut a.b.c.d
d

2
คุณต้องการอัญประกาศรอบอาร์กิวเมนต์echoเพื่อให้สิ่งนี้ทำงานได้อย่างน่าเชื่อถือและแข็งแกร่ง ดูstackoverflow.com/questions/10067266/…
tripleee

0

หากคุณมีไฟล์ชื่อ filelist.txt ที่เป็นรายการพา ธ ดังต่อไปนี้: c: /dir1/dir2/file1.h c: /dir1/dir2/dir3/file2.h

จากนั้นคุณสามารถทำได้: rev filelist.txt | ตัด -d "/" -f1 | การหมุนรอบ


0

การเพิ่มวิธีการในคำถามเก่านี้เพื่อความสนุก:

$ cat input.file # file containing input that needs to be processed
a;b;c;d;e
1;2;3;4;5
no delimiter here
124;adsf;15454
foo;bar;is;null;info

$ cat tmp.sh # showing off the script to do the job
#!/bin/bash
delim=';'
while read -r line; do  
    while [[ "$line" =~ "$delim" ]]; do
        line=$(cut -d"$delim" -f 2- <<<"$line")
    done
    echo "$line"
done < input.file

$ ./tmp.sh # output of above script/processed input file
e
5
no delimiter here
15454
info

นอกจาก bash จะใช้การตัดเท่านั้น ฉันเดาว่า


ทำไมไม่เพียงแค่ลบการตัดออกอย่างสมบูรณ์และใช้เพียง bash ... x] while read -r line; do echo ${line/*;}; done <input.fileให้ผลลัพธ์เดียวกัน
Kaffe Myers

-1

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

$ ans="a, b"
$ ans+=" "; echo ${ans} | tr ',' ' ' | tr -s ' ' | cut -d' ' -f2
b

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