วิธีแยกอักขระสองตัวแรกของสตริงในเชลล์สคริปต์


123

ตัวอย่างเช่นกำหนด:

USCAGoleta9311734.5021-120.1287855805

ฉันต้องการแยกเพียง:

US

6
ขอบคุณทุกคน ฉันลงเอยด้วยการใช้ 'cut -c1-2' โดยสุจริตฉันไม่รู้ด้วยซ้ำว่า 'ตัด' อยู่ที่นั่น ฉันอยากจะบอกว่าฉันค่อนข้างมีประสบการณ์ในบรรทัดคำสั่ง - แต่เห็นได้ชัดว่าฉันมีอะไรให้เรียนรู้มากมาย
Greg

1
@Greg โปรดทราบว่าการตัดจะทำงานเป็นกระบวนการแยกต่างหาก - จะช้ากว่าโซลูชันทุบตีภายในที่ฉันโพสต์ไว้ข้างในคำตอบของฉัน นั่นจะไม่สร้างความแตกต่างใด ๆ เว้นแต่คุณจะประมวลผลชุดข้อมูลขนาดใหญ่ แต่คุณต้องจำไว้
paxdiablo

แก้ไขจริงๆแล้วฉันคิดว่าโค้ดบรรทัดนี้น่าจะถูกเรียกใช้ประมาณ 50,000 ครั้งต่อรายงาน ดังนั้นฉันอาจจะใช้วิธี Bash ภายในซึ่งอย่างที่คุณบอกจะช่วยประหยัดทรัพยากรที่จำเป็นมาก
Greg

คำตอบ:


180

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

pax> long="USCAGol.blah.blah.blah"
pax> short="${long:0:2}" ; echo "${short}"
US

สิ่งนี้จะตั้งshortให้เป็นอักขระสองตัวแรกของlong. ถ้าlongสั้นกว่าสองอักขระshortจะเหมือนกัน

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

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

pax> long="A"
pax> tmpstr="${long}.."
pax> short="${tmpstr:0:2}" ; echo "${short}"
A.

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


ต้องบอกว่ามีหลายวิธีในการดำเนินการกับโปรแกรมภายนอก (เช่นถ้าคุณไม่มีbashให้คุณใช้งาน) ซึ่งบางวิธี ได้แก่ :

short=$(echo "${long}" | cut -c1-2)
short=$(echo "${long}" | head -c2)
short=$(echo "${long}" | awk '{print substr ($0, 0, 2)}'
short=$(echo "${long}" | sed 's/^\(..\).*/\1/')

สองตัวแรก ( cutและhead) เหมือนกันสำหรับสตริงบรรทัดเดียว - โดยพื้นฐานแล้วทั้งคู่จะให้อักขระสองตัวแรกแก่คุณ ซึ่งแตกต่างกันตรงที่cutจะให้อักขระสองตัวแรกของแต่ละบรรทัดและheadจะให้อักขระสองตัวแรกของอินพุตทั้งหมด

ตัวที่สามใช้awkฟังก์ชันสตริงย่อยเพื่อแยกอักขระสองตัวแรกและตัวที่สี่ใช้การsedจับกลุ่ม (โดยใช้()และ\1) เพื่อจับอักขระสองตัวแรกและแทนที่ทั้งบรรทัดด้วย ทั้งคู่คล้ายกับcut- ส่งอักขระสองตัวแรกของแต่ละบรรทัดในอินพุต

สิ่งนี้ไม่สำคัญหากคุณแน่ใจว่าข้อมูลที่คุณป้อนเป็นบรรทัดเดียวทั้งหมดนี้มีผลเหมือนกัน


ผมค่อนข้างจะใช้printf '%s'แทนechoในกรณีที่มีตัวอักษรแปลกในสตริง: stackoverflow.com/a/40423558/895245สำหรับ POSIX หมกมุ่น: head -cไม่ POSIX, cut -cและawk substrจะsed \1ไม่แน่ใจว่า
Ciro Santilli 郝海东冠状病六四事件法轮功

1
@CiroSantilli 新疆改造中心 996ICU 六四事件โดยใช้ printf คุณไม่จำเป็นต้องมีโปรแกรมเพิ่มเติม ดูคำตอบของฉัน
bschlueter

60

วิธีที่ง่ายที่สุดคือ

${string:position:length}

โดยที่สิ่งนี้แยก$lengthสตริงย่อยจาก$stringที่$position.

นี่คือ bash builtin ดังนั้นจึงไม่จำเป็นต้องใช้ awk หรือ sed


นี่เป็นวิธีที่สั้นหวานและง่ายที่สุดในการรับสตริงย่อย
ani627

34

คุณได้รับคำตอบที่ดีหลายคนและฉันไปกับทุบตีในตัวเอง แต่เนื่องจากคุณถามเกี่ยวกับsedและawkและ ( เกือบโซลูชั่น) ไม่มีใครที่นำเสนอบนพื้นฐานของพวกเขาผมให้คุณเหล่านี้:

echo "USCAGoleta9311734.5021-120.1287855805" | awk '{print substr($0,0,2)}'

และ

echo "USCAGoleta9311734.5021-120.1287855805" | sed 's/\(^..\).*/\1/'

สิ่งที่awkควรจะชัดเจนพอสมควร แต่นี่คือคำอธิบายsed:

  • แทนที่ "s /"
  • กลุ่ม "()" ของอักขระสองตัว ".. " โดยเริ่มต้นที่จุดเริ่มต้นของบรรทัด "^" และตามด้วยอักขระใด ๆ "" ซ้ำศูนย์หรือมากกว่าครั้ง "*" (จำเป็นต้องใช้แบ็กสแลชเพื่อหลีกเลี่ยงอักขระพิเศษบางตัว)
  • โดย "/" เนื้อหาของกลุ่มแรก (และเฉพาะในกรณีนี้) (ในที่นี้แบ็กสแลชคือ Escape พิเศษที่อ้างถึงนิพจน์ย่อยที่ตรงกัน)
  • เสร็จสิ้น "/"

1
ในสตริง awk เริ่มต้นที่ดัชนี 1 ดังนั้นคุณควรใช้substr($0,1,2).
Isaac

8

หากคุณเข้าbashมาคุณสามารถพูดว่า:

bash-3.2$ var=abcd
bash-3.2$ echo ${var:0:2}
ab

นี่อาจเป็นเพียงสิ่งที่คุณต้องการ ...


คำตอบที่ง่ายและง่ายที่สุด! ทำงานเหมือนมีเสน่ห์
aloha



5

colrm - ลบคอลัมน์ออกจากไฟล์

หากต้องการเว้นสองตัวอักษรแรกเพียงแค่ลบคอลัมน์ที่เริ่มจาก 3

cat file | colrm 3


2

หากคุณต้องการใช้เชลล์สคริปต์และไม่พึ่งพาส่วนขยายที่ไม่ใช่ posix (เช่นที่เรียกว่า bashisms) คุณสามารถใช้เทคนิคที่ไม่ต้องใช้เครื่องมือภายนอกเช่น grep, sed, cut, awk เป็นต้น ทำให้สคริปต์ของคุณมีประสิทธิภาพน้อยลง บางทีประสิทธิภาพและความสามารถในการพกพา posix ไม่สำคัญในกรณีการใช้งานของคุณ แต่ในกรณีที่เป็น (หรือเป็นนิสัยที่ดี) คุณสามารถใช้วิธีตัวเลือกการขยายพารามิเตอร์ต่อไปนี้เพื่อแยกอักขระสองตัวแรกของตัวแปรเชลล์:

$ sh -c 'var=abcde; echo "${var%${var#??}}"'
ab

ซึ่งใช้การขยายพารามิเตอร์ "คำนำหน้าน้อยที่สุด"เพื่อลบอักขระสองตัวแรก (ซึ่งเป็น${var#??}ส่วนหนึ่ง) จากนั้นขยายพารามิเตอร์ "คำต่อท้ายที่เล็กที่สุด" ( ${var%ส่วน) เพื่อลบสตริงอักขระสองตัวทั้งหมด แต่แรกออกจากอักขระดั้งเดิม ความคุ้มค่า

วิธีนี้เคยอธิบายไว้ในคำตอบของคำถาม "Shell = Check ว่าขึ้นต้นด้วย #" หรือไม่ คำตอบนั้นยังอธิบายถึงวิธีการขยายพารามิเตอร์ที่คล้ายกันสองวิธีซึ่งสามารถใช้ในบริบทที่แตกต่างกันเล็กน้อยซึ่งเป็นวิธีที่ใช้กับคำถามเดิมที่นี่


คำตอบที่ดีที่สุดควรอยู่ด้านบน ไม่มีส้อมไม่มีการทุบตี ใช้งานได้แม้กับเปลือกหอยขนาดเล็กเช่นเส้นประ
ออก

1

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

strEcho='echo ${str:0:2}' # '${str:2}' if you want to skip the first two characters and keep the rest
bash -c "str=\"$strFull\";$strEcho;"

วิธีนี้ใช้วิธีเดียวกับคำตอบหลักโดยจะเรียกใช้bashหากคุณยังไม่ได้ใช้งานเท่านั้น
palswim

น่าเสียดายที่สิ่งนี้มาพร้อมกับค่าใช้จ่ายทั้งหมดในการเรียกใช้กระบวนการอื่น แต่บางครั้งค่าใช้จ่ายนั้นก็ไม่สำคัญเท่ากับความเรียบง่ายและความคุ้นเคย
palswim

1

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

head -c 2 <( echo 'USCAGoleta9311734.5021-120.1287855805')

echo 'USCAGoleta9311734.5021-120.1287855805' | dd bs=2 count=1 status=none

sed -e 's/^\(.\{2\}\).*/\1/;' <( echo 'USCAGoleta9311734.5021-120.1287855805')

cut -c 1-2 <( echo 'USCAGoleta9311734.5021-120.1287855805')

python -c "print(r'USCAGoleta9311734.5021-120.1287855805'[0:2])"

ruby -e 'puts "USCAGoleta9311734.5021-120.1287855805"[0..1]'


0

ถ้า mystring = USCAGoleta9311734.5021-120.1287855805

print substr(mystring,0,2)

จะพิมพ์เรา

โดยที่ 0 คือตำแหน่งเริ่มต้นและ 2 คือวิธีการอ่านอักขระผู้ชาย


บอกว่า ... นั่นไม่ใช่ GW-BASIC ใช่ไหม awkโอ้รอว่า ขอโทษทีแรกบอกไม่ได้
หยุดชั่วคราวจนกว่าจะมีประกาศอีกครั้ง

0

นี่คือสิ่งที่คุณต้องการหรือไม่?

my $string = 'USCAGoleta9311734.5021-120.1287855805';

my $first_two_chars = substr $string, 0, 2;

อ้างอิง: substr


1
เนื่องจากเขา / เธอมีแนวโน้มที่จะเรียกสิ่งนี้จากเปลือกหอยรูปแบบที่ดีกว่าจะเป็นperl -e 'print substr $ARGV[0], 0, 2' 'USCAGoleta9311734.5021-120.1287855805'
Chas Owens
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.