แยกสตริงด้วยตัวคั่นและรับองค์ประกอบ N-th


คำตอบ:


106

ใช้cutกับ_เป็นตัวคั่นสนามและได้รับเขตข้อมูลที่ต้องการ:

A="$(cut -d'_' -f2 <<<'one_two_three_four_five')"
B="$(cut -d'_' -f4 <<<'one_two_three_four_five')"

คุณยังสามารถใช้echoและไพพ์แทนสตริงที่นี่:

A="$(echo 'one_two_three_four_five' | cut -d'_' -f2)"
B="$(echo 'one_two_three_four_five' | cut -d'_' -f4)"

ตัวอย่าง:

$ s='one_two_three_four_five'

$ A="$(cut -d'_' -f2 <<<"$s")"
$ echo "$A"
two

$ B="$(cut -d'_' -f4 <<<"$s")"
$ echo "$B"
four

มีทางเลือกอื่นอีกไหม? ฉันใช้ ksh (ไม่ใช่ bsh) และส่งคืน ksh: ข้อผิดพลาดทางไวยากรณ์: `<'ไม่คาดคิด
อเล็กซ์

@Alex ตรวจสอบการแก้ไขของฉัน
heemayl

คำตอบที่ดีฉันมีคำถามเล็กน้อย: จะเกิดอะไรขึ้นถ้าตัวแปร "$ s" ของคุณเป็นโฟลเดอร์พา ธ เมื่อฉันพยายามที่จะตัดโฟลเดอร์พา ธ ฉันชอบสิ่งต่อไปนี้: `$ FILE = my_user / my_folder / [file] *` $ echo $FILE my_user/my_folder/file.csv $ A="$(cut -d'/' -f2 <<<"$FILE")" $ echo $A [file]* คุณรู้ไหมว่าเกิดอะไรขึ้นที่นี่
Henry Navarro

1
และถ้าคุณต้องการฟิลด์สุดท้ายใช้เพียงเชลล์บิวด์อิน - โดยไม่จำเป็นต้องระบุตำแหน่งหรือเมื่อคุณไม่ทราบจำนวนฟิลด์:echo "${s##*_}"
Amit Naidu

19

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

string='one_two_three_four_five'
remainder="$string"
first="${remainder%%_*}"; remainder="${remainder#*_}"
second="${remainder%%_*}"; remainder="${remainder#*_}"
third="${remainder%%_*}"; remainder="${remainder#*_}"
fourth="${remainder%%_*}"; remainder="${remainder#*_}"

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

string='one_two_three_four_five'
set -f; IFS='_'
set -- $string
second=$2; fourth=$4
set +f; unset IFS

สิ่งนี้ปิดบังพารามิเตอร์ตำแหน่ง หากคุณทำสิ่งนี้ในฟังก์ชันเฉพาะพารามิเตอร์ตำแหน่งของฟังก์ชันเท่านั้นที่จะได้รับผลกระทบ

อีกวิธีหนึ่งคือการใช้readbuiltin

IFS=_ read -r first second third fourth trail <<'EOF'
one_two_three_four_five
EOF

การใช้unset IFSไม่ได้กลับIFSไปเป็นค่าเริ่มต้น หากหลังจากนั้นบางคนOldIFS="$IFS"จะมีค่า Null ใน OldIFS นอกจากนี้ยังสมมติว่าค่าก่อนหน้าของ IFS เป็นค่าเริ่มต้นซึ่งเป็นไปได้มาก (และมีประโยชน์) ที่จะไม่เป็น ทางออกที่ถูกต้องเพียงอย่างเดียวคือการจัดเก็บold="$IFS"และกู้คืนในภายหลังด้วย IFS = "$ old" หรือ ... (...)ใช้เปลือกย่อย หรือดีกว่ายังอ่านคำตอบของฉัน
sorontar

@sorontar unset IFSไม่ได้คืนค่าIFSเป็นค่าเริ่มต้น แต่จะคืนค่าการแบ่งเขตข้อมูลเป็นผลเริ่มต้น ใช่มันเป็นข้อ จำกัด แต่โดยทั่วไปแล้วเป็นข้อปฏิบัติที่ยอมรับได้ ปัญหาเกี่ยวกับ subshell คือเราต้องดึงข้อมูลออกมา readฉันจะแสดงวิธีการแก้ปัญหาที่ไม่ได้เปลี่ยนสถานะในตอนท้ายด้วย (ใช้งานได้ในเชลล์ POSIX แต่ IIRC ไม่ได้อยู่ในเชลล์เป้าหมายเพราะมันจะทำงานreadในเชลล์ย่อยเนื่องจากเอกสารที่นี่) การใช้<<<คำตอบที่คุณใช้เป็นตัวแปรที่ใช้งานได้เฉพาะใน ksh / bash / zsh
กิลส์

ฉันไม่เห็นปัญหาแม้แต่กับ att หรือ heirloom shell เกี่ยวกับ subshell เชลล์ทั้งหมดที่ทดสอบ (รวมถึง bourne เก่า) ให้ค่าที่ถูกต้องในเชลล์หลัก
sorontar

จะเกิดอะไรขึ้นถ้าเส้นทางของฉันเป็นuser/my_folder/[this_is_my_file]*อย่างไร สิ่งที่ฉันได้รับเมื่อฉันทำตามขั้นตอนเหล่านี้คือ[this_is_my_file]*
Henry Navarro

@HenryNavarro ผลลัพธ์นี้ไม่ตรงกับตัวอย่างของรหัสใด ๆ ในคำตอบของฉัน /ไม่มีของพวกเขาจะทำอะไรเป็นพิเศษเกี่ยวกับ
Gilles

17

ต้องการที่จะเห็นawkคำตอบดังนั้นนี่คือหนึ่ง:

A=$(awk -F_ '{print $2}' <<< 'one_two_three_four_five')
B=$(awk -F_ '{print $4}' <<< 'one_two_three_four_five')

1
และถ้าคุณต้องการชิ้นสุดท้าย - โดยไม่จำเป็นต้องระบุตำแหน่งหรือเมื่อคุณไม่ทราบจำนวนของฟิลด์:awk -F_ '{print $NF}' <<< 'one_two_3_4_five'
Amit Naidu

8

วิธีที่ง่ายที่สุด (สำหรับเชลล์ที่มี <<<) คือ:

 IFS='_' read -r a second a fourth a <<<"$string"

การใช้ตัวแปรชั่วคราว$aแทน$_เพราะเชลล์หนึ่งตัวบ่น

ในสคริปต์เต็ม:

 string='one_two_three_four_five'
 IFS='_' read -r a second a fourth a <<<"$string"
 echo "$second $fourth"

ไม่มีการเปลี่ยน IFS ไม่ใช่ปัญหากับset -f(การขยายชื่อเส้นทาง) ไม่มีการเปลี่ยนแปลงพารามิเตอร์ตำแหน่ง ("$ @")


สำหรับวิธีแก้ปัญหาแบบพกพาสำหรับเชลล์ทั้งหมด (ใช่แล้ว POSIX ทั้งหมดรวมอยู่) โดยไม่ต้องเปลี่ยน IFS หรือset -fใช้ heredoc (ซับซ้อนกว่านี้เล็กน้อย) เทียบเท่า:

string='one_two_three_four_five'

IFS='_' read -r a second a fourth a <<-_EOF_
$string
_EOF_

echo "$second $fourth"

เข้าใจว่าโซลูชันนี้ (ทั้ง here-doc และการใช้<<<จะลบ newlines ต่อท้ายทั้งหมด
และว่าสิ่งนี้ถูกออกแบบมาเพื่อเนื้อหาตัวแปร "หนึ่งซับ"
โซลูชั่นสำหรับ multi-liners เป็นไปได้ แต่ต้องการโครงสร้างที่ซับซ้อนมากขึ้น


วิธีการแก้ปัญหาที่ง่ายมากเป็นไปได้ใน bash รุ่น 4.4

readarray -d _ -t arr <<<"$string"

echo "array ${arr[1]} ${arr[3]}"   # array numbers are zero based.

ไม่มีความเทียบเท่าสำหรับ POSIX เชลล์เนื่องจาก POSIX เชลล์จำนวนมากไม่มีอาร์เรย์

สำหรับเชลล์ที่มีอาร์เรย์อาจจะง่ายเหมือน:
(ทดสอบการทำงานใน attsh, lksh, mksh, ksh และ bash)

set -f; IFS=_; arr=($string)

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

string='one_* *_three_four_five'

case $- in
    *f*) noglobset=true; ;;
    *) noglobset=false;;
esac

oldIFS="$IFS"

set -f; IFS=_; arr=($string)

if $noglobset; then set -f; else set +f; fi

echo "two=${arr[1]} four=${arr[3]}"

ใน zsh อาร์เรย์เริ่มต้นที่ 1 และไม่แยกสตริงตามค่าเริ่มต้น
ดังนั้นจึงจำเป็นต้องทำการเปลี่ยนแปลงบางอย่างเพื่อให้การทำงานเป็นแบบ zsh นี้


โซลูชันที่ใช้งานread ง่ายตราบใดที่ OP ไม่ต้องการแยกองค์ประกอบที่ 76 และ 127 ออกมาจากสตริงที่ยาว ...
don_crissti

@don_crissti ใช่แน่นอน แต่โครงสร้างที่คล้ายกัน: readarrayอาจใช้งานได้ง่ายกว่าสำหรับสถานการณ์นั้น
sorontar

@don_crissti ฉันยังเพิ่มโซลูชันอาร์เรย์สำหรับเชลล์ที่มีอาร์เรย์ด้วย สำหรับ POSIX shells, ดี, ไม่มีอาร์เรย์, พารามิเตอร์ตำแหน่งสูงถึง 127 องค์ประกอบไม่ใช่วิธี "ง่าย ๆ " โดยการวัดใด ๆ
sorontar

2

ด้วยzshคุณสามารถแยกสตริง (บน_) เป็นอาร์เรย์:

elements=(${(s:_:)string})

จากนั้นเข้าถึงแต่ละ / องค์ประกอบใด ๆ ผ่านดัชนีอาเรย์:

print -r ${elements[4]}

เก็บไว้ในใจว่าในzsh(เหมือนksh/ bash) ดัชนีอาร์เรย์เริ่มต้นที่ 1


โปรดจำไว้ว่าให้เพิ่มset -fคำเตือนในโซลูชันแรก ... ดอกจัน*อาจจะ?
sorontar

@ Sorontar - ทำไมคุณถึงคิดว่าฉันต้องการset -f? ฉันไม่ได้ใช้/read IFSลองใช้วิธีแก้ปัญหาของฉันกับสตริง*_*_*หรืออะไรก็ตาม ...
don_crissti

ไม่ใช่สำหรับ zsh แต่ผู้ใช้ขอวิธีแก้ปัญหา ksh ดังนั้นเขาอาจลองใช้มันในเชลล์นั้น คำเตือนจะช่วยให้เขาหลีกเลี่ยงปัญหา
sorontar

1

อนุญาตให้ใช้วิธีแก้ปัญหาแบบหลามหรือไม่

# python -c "import sys; print sys.argv[1].split('_')[1]" one_two_three_four_five
two

# python -c "import sys; print sys.argv[1].split('_')[3]" one_two_three_four_five
four

ไม่คำตอบที่ไม่ดีไม่ดี
Raj Kumar

0

อีกตัวอย่าง awk; ง่ายต่อการเข้าใจ

A=\`echo one_two_three_four_five | awk -F_ '{print $1}'\`  
B=\`echo one_two_three_four_five | awk -F_ '{print $2}'\`  
C=\`echo one_two_three_four_five | awk -F_ '{print $3}'\`  
... and so on...  

สามารถใช้กับตัวแปรได้
สมมติว่า:
this_str = "one_two_three_four_five"
จากนั้นทำงานต่อไปนี้:
A = `echo $ {this_str} | awk -F_ '{พิมพ์ $ 1}' `
B =` echo $ {this_str} | awk -F_ '{พิมพ์ $ 2}' `
C =` echo $ {this_str} | awk -F_ '{พิมพ์ $ 3}' '
... และอื่น ๆ ...

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