ฉันจะแยกสตริงบนตัวคั่นใน Bash ได้อย่างไร


2041

ฉันมีสตริงนี้เก็บไว้ในตัวแปร:

IN="bla@some.com;john@home.com"

ตอนนี้ฉันต้องการแยกสตริงด้วย;ตัวคั่นเพื่อให้ฉันมี:

ADDR1="bla@some.com"
ADDR2="john@home.com"

ฉันไม่ต้องการADDR1และADDR2ตัวแปร หากพวกเขาเป็นองค์ประกอบของอาเรย์ที่ดียิ่งขึ้น


หลังจากข้อเสนอแนะจากคำตอบด้านล่างฉันลงเอยด้วยสิ่งต่อไปนี้ซึ่งเป็นสิ่งที่ฉันเป็นหลังจาก:

#!/usr/bin/env bash

IN="bla@some.com;john@home.com"

mails=$(echo $IN | tr ";" "\n")

for addr in $mails
do
    echo "> [$addr]"
done

เอาท์พุท:

> [bla@some.com]
> [john@home.com]

มีวิธีการแก้ปัญหาที่เกี่ยวข้องกับการตั้งค่าเป็นInternal_field_separator (ไอเอฟเอ) ;เพื่อ ฉันไม่แน่ใจว่าเกิดอะไรขึ้นกับคำตอบนั้นคุณจะรีเซ็ตอย่างไรIFSกลับเป็นค่าเริ่มต้นได้อย่างไร

RE: IFSวิธีแก้ปัญหาฉันลองใช้ดูแล้วมันได้ผลฉันใช้รุ่นเก่าIFSแล้วคืนค่า:

IN="bla@some.com;john@home.com"

OIFS=$IFS
IFS=';'
mails2=$IN
for x in $mails2
do
    echo "> [$x]"
done

IFS=$OIFS

BTW เมื่อฉันพยายาม

mails2=($IN)

ฉันได้รับสตริงแรกเท่านั้นเมื่อพิมพ์ในลูปโดยไม่ต้องวงเล็บรอบ$INมันทำงาน


14
เกี่ยวกับ "Edit2" ของคุณ: คุณสามารถเพียงแค่ "ยกเลิกการตั้งค่า IFS" และมันจะกลับสู่สถานะเริ่มต้น ไม่จำเป็นต้องบันทึกและกู้คืนมันอย่างชัดเจนเว้นแต่คุณจะมีเหตุผลบางอย่างที่คาดว่าจะถูกตั้งเป็นค่าที่ไม่ใช่ค่าเริ่มต้น ยิ่งไปกว่านั้นถ้าคุณทำสิ่งนี้ภายในฟังก์ชั่น (และถ้าไม่ใช่, ทำไมล่ะ?) คุณสามารถตั้งค่า IFS เป็นตัวแปรโลคอลและมันจะกลับไปเป็นค่าก่อนหน้าเมื่อคุณออกจากฟังก์ชั่น
Brooks Moses

19
@BrooksMoses: (a) +1 สำหรับการใช้local IFS=...หากเป็นไปได้ (b) -1 สำหรับunset IFSนี่ไม่ได้รีเซ็ต IFS เป็นค่าเริ่มต้นอย่างแน่นอน แต่ฉันเชื่อว่า IFS ที่ยังไม่ได้ตั้งค่าจะทำงานเหมือนกับค่าเริ่มต้นของ IFS ($ '\ t \ n') แต่ดูเหมือนว่าการปฏิบัติที่ไม่ถูกต้อง สมมติว่าสุ่มสี่สุ่มห้าว่าโค้ดของคุณจะไม่ถูกเรียกใช้ด้วย IFS ที่ตั้งเป็นค่าที่กำหนดเอง (c) แนวคิดอื่นคือเรียกใช้ subshell: (IFS=$custom; ...)เมื่อ subshell ออกจาก IFS จะกลับไปใช้สิ่งที่มันเคยเป็นมา
dubiousjim

ฉันแค่ต้องการดูเส้นทางที่รวดเร็วในการตัดสินใจว่าจะโยนไฟล์ปฏิบัติการruby -e "puts ENV.fetch('PATH').split(':')"ได้อย่างไร หากคุณต้องการใช้วิธีทุบตีแท้ๆจะไม่ช่วย แต่การใช้ภาษาสคริปต์ใด ๆที่มีการแยกในตัวนั้นง่ายกว่า
nicooga

4
for x in $(IFS=';';echo $IN); do echo "> [$x]"; done
user2037659

2
เพื่อที่จะบันทึกเป็นอาร์เรย์ฉันต้องวางวงเล็บอีกชุดหนึ่งไว้และเปลี่ยน\nเป็นช่องว่าง mails=($(echo $IN | tr ";" " "))ดังนั้นบรรทัดสุดท้ายคือ ดังนั้นตอนนี้ฉันสามารถตรวจสอบองค์ประกอบของmailsโดยใช้สัญกรณ์อาร์เรย์mails[index]หรือเพียงวนซ้ำในวงวน
afranques

คำตอบ:


1234

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

IFS=';' read -ra ADDR <<< "$IN"
for i in "${ADDR[@]}"; do
    # process "$i"
done

มันจะแยกรายการหนึ่งบรรทัดคั่นด้วย;ผลักเข้าไปในอาร์เรย์ เนื้อหาสำหรับการประมวลผลทั้งหมดของ$INแต่ละครั้งอินพุตหนึ่งบรรทัดคั่นด้วย;:

 while IFS=';' read -ra ADDR; do
      for i in "${ADDR[@]}"; do
          # process "$i"
      done
 done <<< "$IN"

22
นี่อาจเป็นวิธีที่ดีที่สุด IFS จะคงอยู่นานแค่ไหนในค่าปัจจุบันมันจะทำให้รหัสของฉันยุ่งเหยิงโดยการตั้งค่าเมื่อมันไม่ควรจะเป็นและฉันจะรีเซ็ตมันเมื่อฉันทำมันได้หรือไม่
คริส Lutz

7
ขณะนี้หลังจากการแก้ไขนำไปใช้เฉพาะภายในระยะเวลาของการอ่านคำสั่ง :)
โยฮันเน Schaub - litb

14
คุณสามารถอ่านทุกอย่างพร้อมกันได้โดยไม่ต้องใช้การวนรอบสักครู่: read -r -d '' -a addr <<< "$ in" # The -d '' เป็นกุญแจสำคัญที่นี่มันบอกให้อ่านไม่หยุดที่บรรทัดใหม่แรก ( ซึ่งเป็นค่าเริ่มต้น -d) แต่จะดำเนินการต่อไปจนถึง EOF หรือไบต์ NULL (ซึ่งจะเกิดขึ้นในข้อมูลไบนารีเท่านั้น)
lhunath

55
@LucaBorrione การตั้งค่าIFSในบรรทัดเดียวreadกับที่ไม่มีเครื่องหมายอัฒภาคหรือตัวคั่นอื่น ๆ เมื่อเทียบกับในคำสั่งที่แยกต่างหากขอบเขตมันไปที่คำสั่งนั้น - ดังนั้นจึงเสมอ "คืน"; คุณไม่จำเป็นต้องทำอะไรด้วยตนเอง
Charles Duffy

5
@imagineerThis มีข้อผิดพลาดเกี่ยวกับ herestrings และการเปลี่ยนแปลงในท้องถิ่นของ IFS ที่ต้อง$INมีการยกมา ข้อผิดพลาดได้รับการแก้ไขในbash4.3
chepner

973

นำมาจากอาร์เรย์สคริปต์แยกเปลือก Bash :

IN="bla@some.com;john@home.com"
arrIN=(${IN//;/ })

คำอธิบาย:

สิ่งก่อสร้างนี้แทนที่การเกิดขึ้นทั้งหมดของ';'(เริ่มต้น//หมายถึงการแทนที่ทั่วโลก) ในสตริงINด้วย' ' (ช่องว่างเดียว) จากนั้นตีความสตริงที่คั่นด้วยช่องว่างเป็นอาร์เรย์ (นั่นคือสิ่งที่วงเล็บล้อมรอบทำ)

ไวยากรณ์ที่ใช้ภายในเครื่องหมายปีกกาเพื่อแทนที่';'อักขระแต่ละตัวด้วย' 'อักขระเรียกว่าการขยายพารามิเตอร์การขยายตัวพารามิเตอร์

มี gotchas ทั่วไปบางอย่าง:

  1. หากสตริงเดิมมีช่องว่างคุณจะต้องใช้IFS :
    • IFS=':'; arrIN=($IN); unset IFS;
  2. หากสตริงเดิมมีช่องว่างและตัวคั่นเป็นบรรทัดใหม่คุณสามารถตั้งค่าIFSด้วย:
    • IFS=$'\n'; arrIN=($IN); unset IFS;

84
ฉันต้องการเพิ่ม: นี่คือสิ่งที่ง่ายที่สุดของทั้งหมดคุณสามารถเข้าถึงองค์ประกอบอาร์เรย์ด้วย $ {arrIN [1]} (เริ่มต้นจากศูนย์แน่นอน)
Oz123

26
พบแล้ว: เทคนิคการแก้ไขตัวแปรภายใน $ {} เป็นที่รู้จักกันในชื่อ 'การขยายพารามิเตอร์'
KomodoDave

23
ไม่ฉันไม่คิดว่ามันจะทำงานเมื่อมีช่องว่างปรากฏอยู่ ... มันเปลี่ยน ',' เป็น '' แล้วสร้างอาร์เรย์ที่คั่นด้วยช่องว่าง
อีธาน

12
รวบรัดมาก แต่มีข้อแม้สำหรับการใช้งานทั่วไป : เชลล์ใช้การแยกคำและการขยายตัวของสตริงซึ่งอาจไม่พึงประสงค์ แค่ลองด้วย IN="bla@some.com;john@home.com;*;broken apart". กล่าวโดยย่อ: วิธีนี้จะแตกถ้าโทเค็นของคุณมีช่องว่างและ / หรือตัวอักษรฝังอยู่ เช่น*ที่เกิดขึ้นเพื่อสร้างชื่อไฟล์การจับคู่โทเค็นในโฟลเดอร์ปัจจุบัน
mklement0

53
นี่เป็นวิธีการที่ไม่ดีด้วยเหตุผลอื่น: ตัวอย่างเช่นหากสตริงของคุณมีอยู่ไฟล์;*;นั้น*จะถูกขยายเป็นรายการชื่อไฟล์ในไดเรกทอรีปัจจุบัน -1
Charles Duffy

249

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

for i in $(echo $IN | tr ";" "\n")
do
  # process
done

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


คุณควรเก็บคำตอบของ IFS ไว้ มันสอนอะไรบางอย่างที่ฉันไม่รู้และมันก็สร้างอาร์เรย์ขึ้นมาแน่นอน
คริส Lutz

ฉันเห็น. ใช่ฉันพบว่าทำการทดลองที่โง่ ๆ เหล่านี้ฉันจะได้เรียนรู้สิ่งใหม่ทุกครั้งที่ฉันพยายามตอบคำถาม ฉันได้แก้ไขสิ่งที่ตามความคิดเห็นของ IRC #bash และลบ :)
โยฮันเน Schaub - litb

33
-1 คุณไม่ทราบแน่ชัดว่ามีการใช้คำเพราะมันมีข้อบกพร่องสองประการในรหัสของคุณ หนึ่งคือเมื่อคุณไม่ได้อ้างถึง $ IN และอีกอันคือเมื่อคุณแสร้งทำเป็นขึ้นบรรทัดใหม่เป็นตัวคั่นเดียวที่ใช้ใน wordplitting คุณกำลังวนซ้ำทุกคำในในไม่ใช่ทุกบรรทัดและกำหนดไม่ใช่ทุกองค์ประกอบที่คั่นด้วยเครื่องหมายอัฒภาคแม้ว่ามันอาจดูเหมือนว่าจะมีผลข้างเคียงของการดูเหมือนว่ามันทำงาน
lhunath

3
คุณสามารถเปลี่ยนเป็น echo "$ IN" | tr ';' '\ n' | ในขณะที่อ่าน -r ADDY; ทำ # process "$ ADDY"; ทำเพื่อทำให้เขาโชคดีฉันคิดว่า :) โปรดทราบว่านี่จะแยกและคุณไม่สามารถเปลี่ยนตัวแปรภายนอกจากภายในลูป (นั่นเป็นเหตุผลที่ฉันใช้ไวยากรณ์ <<< "$ IN") แล้ว
Johannes Schaub - litb

8
เพื่อสรุปการอภิปรายในความคิดเห็น: Caveats สำหรับการใช้งานทั่วไป : เชลล์ใช้การแยกคำและการขยายไปยังสตริงซึ่งอาจไม่พึงประสงค์; แค่ลองด้วย IN="bla@some.com;john@home.com;*;broken apart". กล่าวโดยย่อ: วิธีนี้จะแตกถ้าโทเค็นของคุณมีช่องว่างและ / หรือตัวอักษรฝัง เช่น*ที่เกิดขึ้นเพื่อสร้างชื่อไฟล์การจับคู่โทเค็นในโฟลเดอร์ปัจจุบัน
mklement0

202

คำตอบที่เข้ากันได้

มีหลายวิธีในการทำเช่นนี้ค่ะ .

อย่างไรก็ตามสิ่งสำคัญคือต้องทราบก่อนว่าbashมีคุณสมบัติพิเศษมากมาย (ที่เรียกว่าbashisms ) ที่ไม่สามารถใช้งานได้.

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

ตัวอย่างเช่น: บนDebian GNU / Linuxของฉันมีเชลล์มาตรฐานที่เรียกว่า; ฉันรู้ว่าหลายคนที่ชอบใช้เปลือกหอยชนิดอื่นที่เรียกว่า; และยังมีเครื่องมือพิเศษที่เรียกว่า ด้วยล่ามเปลือกของเขาเอง ()

ขอสตริง

สตริงที่จะแยกในคำถามข้างต้นคือ:

IN="bla@some.com;john@home.com"

ฉันจะใช้เวอร์ชันที่แก้ไขของสตริงนี้เพื่อให้แน่ใจว่าโซลูชันของฉันมีความทนทานต่อสตริงที่มีช่องว่างซึ่งอาจทำลายโซลูชันอื่น ๆ :

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"

แยกสตริงตามตัวคั่นใน (เวอร์ชั่น> = 4.2)

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

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"

# save original IFS value so we can restore it later
oIFS="$IFS"
IFS=";"
declare -a fields=($IN)
IFS="$oIFS"
unset oIFS

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

IFS=\; read -a fields <<<"$IN"
# after this command, the IFS resets back to its previous value (here, the default):
set | grep ^IFS=
# IFS=$' \t\n'

เราจะเห็นว่าสตริงINนั้นถูกเก็บไว้ในอาร์เรย์ชื่อfieldsแยกบนอัฒภาค:

set | grep ^fields=\\\|^IN=
# fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")
# IN='bla@some.com;john@home.com;Full Name <fulnam@other.org>'

(เรายังสามารถแสดงเนื้อหาของตัวแปรเหล่านี้โดยใช้declare -p:)

declare -p IN fields
# declare -- IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
# declare -a fields=([0]="bla@some.com" [1]="john@home.com" [2]="Full Name <fulnam@other.org>")

โปรดทราบว่าreadเป็นวิธีที่เร็วที่สุดในการแยกเนื่องจากไม่มีส้อมหรือทรัพยากรภายนอกที่เรียกว่า

เมื่อกำหนดอาร์เรย์แล้วคุณสามารถใช้การวนรอบอย่างง่าย ๆ ในการประมวลผลแต่ละฟิลด์ (หรือแทนแต่ละองค์ประกอบในอาร์เรย์ที่คุณกำหนดไว้ตอนนี้)

# `"${fields[@]}"` expands to return every element of `fields` array as a separate argument
for x in "${fields[@]}" ;do
    echo "> [$x]"
    done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

หรือคุณสามารถปล่อยแต่ละฟิลด์จากอาร์เรย์หลังจากประมวลผลโดยใช้วิธีการเลื่อนซึ่งฉันชอบ:

while [ "$fields" ] ;do
    echo "> [$fields]"
    # slice the array 
    fields=("${fields[@]:1}")
    done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

และถ้าคุณต้องการเพียงแค่พิมพ์อาเรย์ง่ายๆคุณไม่จำเป็นต้องวนซ้ำมัน:

printf "> [%s]\n" "${fields[@]}"
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

อัปเดต: ล่าสุด > = 4.4

ในเวอร์ชันที่ใหม่กว่าของbashคุณยังสามารถเล่นกับคำสั่งmapfile:

mapfile -td \; fields < <(printf "%s\0" "$IN")

ไวยากรณ์นี้เก็บรักษาตัวอักษรพิเศษบรรทัดใหม่และฟิลด์ว่าง!

หากคุณไม่ต้องการรวมฟิลด์ว่างคุณสามารถทำสิ่งต่อไปนี้:

mapfile -td \; fields <<<"$IN"
fields=("${fields[@]%$'\n'}")   # drop '\n' added by '<<<'

ด้วยmapfileคุณสามารถข้ามการประกาศอาร์เรย์และ "วน" โดยปริยายบนองค์ประกอบที่คั่นด้วยการเรียกใช้ฟังก์ชันในแต่ละรายการ:

myPubliMail() {
    printf "Seq: %6d: Sending mail to '%s'..." $1 "$2"
    # mail -s "This is not a spam..." "$2" </path/to/body
    printf "\e[3D, done.\n"
}

mapfile < <(printf "%s\0" "$IN") -td \; -c 1 -C myPubliMail

(หมายเหตุ: \0สตริงที่จุดสิ้นสุดของรูปแบบไม่มีประโยชน์หากคุณไม่สนใจฟิลด์ว่างที่ท้ายสตริงหรือไม่ปรากฏอยู่)

mapfile < <(echo -n "$IN") -td \; -c 1 -C myPubliMail

# Seq:      0: Sending mail to 'bla@some.com', done.
# Seq:      1: Sending mail to 'john@home.com', done.
# Seq:      2: Sending mail to 'Full Name <fulnam@other.org>', done.

หรือคุณสามารถใช้<<<และในส่วนของฟังก์ชั่นนั้นมีการประมวลผลบางอย่างเพื่อวางบรรทัดใหม่ที่มันเพิ่ม:

myPubliMail() {
    local seq=$1 dest="${2%$'\n'}"
    printf "Seq: %6d: Sending mail to '%s'..." $seq "$dest"
    # mail -s "This is not a spam..." "$dest" </path/to/body
    printf "\e[3D, done.\n"
}

mapfile <<<"$IN" -td \; -c 1 -C myPubliMail

# Renders the same output:
# Seq:      0: Sending mail to 'bla@some.com', done.
# Seq:      1: Sending mail to 'john@home.com', done.
# Seq:      2: Sending mail to 'Full Name <fulnam@other.org>', done.

แยกสตริงตามตัวคั่นใน

หากคุณไม่สามารถใช้งานได้bashหรือถ้าคุณต้องการเขียนสิ่งที่สามารถใช้ได้ในหลาย ๆ เชลล์คุณมักจะไม่ใช้bashisms - ซึ่งรวมถึงอาร์เรย์ที่เราใช้ในการแก้ปัญหาข้างต้น

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

(การขาดวิธีการนี้ในการแก้ไขปัญหาใด ๆ ที่โพสต์จนถึงเป็นเหตุผลหลักที่ฉันเขียนคำตอบนี้)

${var#*SubStr}  # drops substring from start of string up to first occurrence of `SubStr`
${var##*SubStr} # drops substring from start of string up to last occurrence of `SubStr`
${var%SubStr*}  # drops substring from last occurrence of `SubStr` to end of string
${var%%SubStr*} # drops substring from first occurrence of `SubStr` to end of string

ตามที่อธิบายโดยScore_Under :

#และ%ลบสตริงย่อยที่ตรงกันที่สั้นที่สุดที่เป็นไปได้จากจุดเริ่มต้นและจุดสิ้นสุดของสตริงตามลำดับและ

##และ%%ลบสตริงย่อยที่ตรงกันที่ยาวที่สุดที่เป็นไปได้

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

codeblock ด้านล่างใช้งานได้ดี (รวมถึงของ Mac OS bash), และ 's :

IN="bla@some.com;john@home.com;Full Name <fulnam@other.org>"
while [ "$IN" ] ;do
    # extract the substring from start of string up to delimiter.
    # this is the first "element" of the string.
    iter=${IN%%;*}
    echo "> [$iter]"
    # if there's only one element left, set `IN` to an empty string.
    # this causes us to exit this `while` loop.
    # else, we delete the first "element" of the string from IN, and move onto the next.
    [ "$IN" = "$iter" ] && \
        IN='' || \
        IN="${IN#*;}"
  done
# > [bla@some.com]
# > [john@home.com]
# > [Full Name <fulnam@other.org>]

มีความสุข!


15
#, ##, %และ%%แทนมีสิ่งที่เป็น IMO คำอธิบายง่ายต่อการจดจำ (สำหรับวิธีการที่พวกเขาลบ) #และ%ลบการจับคู่สายที่สั้นที่สุดและ##และ%%ลบที่เป็นไปได้ที่ยาวที่สุด
คะแนน _ ต่ำกว่า

1
การIFS=\; read -a fields <<<"$var"ขึ้นบรรทัดใหม่ล้มเหลวและเพิ่มขึ้นบรรทัดใหม่ โซลูชันอื่นจะลบฟิลด์ว่างต่อท้าย
ไอแซค

ตัวคั่นเชลล์เป็นคำตอบที่หรูหราที่สุด
Eric Chen

สามารถใช้ทางเลือกสุดท้ายกับรายการตัวคั่นฟิลด์ที่ตั้งค่าไว้ที่อื่นได้หรือไม่? ตัวอย่างเช่นฉันหมายถึงการใช้สิ่งนี้เป็นเชลล์สคริปต์และส่งรายการตัวคั่นฟิลด์เป็นพารามิเตอร์ตำแหน่ง
sancho.s ReinstateMonicaCellio

ใช่อยู่ในวงวน:for sep in "#" "ł" "@" ; do ... var="${var#*$sep}" ...
F. Hauri

184

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

ในกรณีของการแยกตัวอย่างเฉพาะนี้ลงในอาร์เรย์สคริปต์ทุบตีtrอาจมีประสิทธิภาพมากกว่า แต่cutสามารถใช้ได้และมีประสิทธิภาพมากกว่าหากคุณต้องการดึงเขตข้อมูลเฉพาะจากกึ่งกลาง

ตัวอย่าง:

$ echo "bla@some.com;john@home.com" | cut -d ";" -f 1
bla@some.com
$ echo "bla@some.com;john@home.com" | cut -d ";" -f 2
john@home.com

เห็นได้ชัดว่าคุณสามารถใส่ลงในลูปและวนซ้ำพารามิเตอร์ -f เพื่อดึงแต่ละฟิลด์เป็นอิสระ

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

2015-04-27|12345|some action|an attribute|meta data

cutมีประโยชน์มากที่จะสามารถใช้catไฟล์นี้และเลือกฟิลด์เฉพาะสำหรับการประมวลผลเพิ่มเติม


6
รุ่งโรจน์สำหรับการใช้cutงานมันเป็นเครื่องมือที่เหมาะสมสำหรับงาน! เคลียร์มากกว่าเชลล์ใด ๆ ในแฮ็ก
MisterMiyagi

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

อยากรู้อยากเห็นฉันกำลังพยายามหลีกเลี่ยงสตริงว่างใน csv ตอนนี้ฉันสามารถชี้ค่า 'คอลัมน์' ที่แน่นอนเช่นกัน ทำงานกับ IFS ที่ใช้ในลูปแล้ว ดีกว่าที่คาดไว้สำหรับสถานการณ์ของฉัน
Louis Loudog Trottier

มีประโยชน์มากสำหรับการดึง ID และ PID เช่นกัน
Milos Grujic

คำตอบนี้เป็นมูลค่าการเลื่อนลงกว่าครึ่งหน้า :)
Gucu112

124

สิ่งนี้ใช้ได้กับฉัน:

string="1;2"
echo $string | cut -d';' -f1 # output is 1
echo $string | cut -d';' -f2 # output is 2

1
แม้ว่าจะใช้ได้กับตัวคั่นอักขระเดียวเท่านั้น แต่นั่นคือสิ่งที่ OP ค้นหา (บันทึกคั่นด้วยเครื่องหมายอัฒภาค)
GuyPaddock

ตอบเมื่อสี่ปีที่แล้วโดย@Ashokและอีกกว่าหนึ่งปีที่แล้วโดย@DougWมากกว่าคำตอบของคุณพร้อมข้อมูลเพิ่มเติม โปรดโพสต์วิธีแก้ไขปัญหาที่แตกต่างจากของคนอื่น
MAChitgarha

90

วิธีการเกี่ยวกับวิธีนี้:

IN="bla@some.com;john@home.com" 
set -- "$IN" 
IFS=";"; declare -a Array=($*) 
echo "${Array[@]}" 
echo "${Array[0]}" 
echo "${Array[1]}" 

แหล่ง


7
+1 ... แต่ฉันจะไม่ตั้งชื่อตัวแปร "Array" ... pet peev ฉันเดา ทางออกที่ดี
Yzmir Ramirez

14
+1 ... แต่ "set" และประกาศ -a นั้นไม่จำเป็น คุณสามารถใช้เช่นกันIFS";" && Array=($IN)
ata

+1 หมายเหตุด้านข้างเท่านั้น: ไม่แนะนำให้เก็บ IFS เก่าไว้หรือไม่แล้วกู้คืนได้หรือไม่ (ดังแสดงโดย stefanB ในการแก้ไขของเขา 3) ผู้คนที่เชื่อมโยงไปถึงที่นี่ (บางครั้งก็แค่คัดลอกและวางวิธีแก้ปัญหา) อาจไม่คิดเกี่ยวกับสิ่งนี้
Luca Borrione

6
-1: อันดับแรก @ata ถูกต้องที่คำสั่งส่วนใหญ่ในนี้ไม่ทำอะไรเลย ข้อที่สองมันใช้การแยกคำเพื่อสร้างอาร์เรย์และไม่ทำอะไรเลยเพื่อยับยั้งการขยายตัวของ glob เมื่อทำเช่นนั้น (ดังนั้นถ้าคุณมีตัวอักษรกลมในองค์ประกอบอาร์เรย์ใด ๆ องค์ประกอบเหล่านั้นจะถูกแทนที่ด้วยชื่อไฟล์ที่ตรงกัน)
Charles Duffy

1
ขอแนะนำให้ใช้:$'...' IN=$'bla@some.com;john@home.com;bet <d@\ns* kl.com>'จากนั้นecho "${Array[2]}"จะพิมพ์สตริงด้วยบรรทัดใหม่ set -- "$IN"เป็นสิ่งจำเป็นในกรณีนี้ ใช่เพื่อป้องกันการขยายตัวของ glob โซลูชันควรรวมset -fไว้ด้วย
John_West

79

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

echo "bla@some.com;john@home.com" | awk -F';' '{print $1,$2}'

จะให้

bla@some.com john@home.com

แน่นอนคุณสามารถจัดเก็บที่อยู่อีเมลแต่ละรายการได้โดยกำหนดฟิลด์การพิมพ์ awk ใหม่


3
หรือง่ายยิ่งขึ้น: echo "bla@some.com; john@home.com" | awk 'BEGIN {RS = ";"} {print}'
Jaro

@Jaro มันทำงานได้อย่างสมบูรณ์แบบสำหรับฉันเมื่อฉันมีสตริงที่มีเครื่องหมายจุลภาคและจำเป็นต้องจัดรูปแบบใหม่เป็นเส้น ขอบคุณ
Aquarelle

มันทำงานในสถานการณ์นี้ -> "echo" $ SPLIT_0 "| awk -F 'inode =' '{พิมพ์ $ 1}'"! ฉันมีปัญหาเมื่อพยายามใช้ atrings ("inode =") แทนที่จะเป็นตัวอักษร (";") $ 1, $ 2, $ 3, $ 4 ถูกกำหนดให้เป็นตำแหน่งในอาร์เรย์! หากมีวิธีการตั้งค่าอาร์เรย์ ... ดีกว่า! ขอบคุณ!
Eduardo Lucio

@EduardoLucio สิ่งที่ฉันคิดคือบางทีคุณสามารถแทนที่ตัวคั่นของคุณinode=เป็น;ตัวอย่างโดยsed -i 's/inode\=/\;/g' your_file_to_processแล้วกำหนด-F';'เมื่อนำไปใช้awkหวังว่าจะสามารถช่วยคุณได้
ตอง

66
echo "bla@some.com;john@home.com" | sed -e 's/;/\n/g'
bla@some.com
john@home.com

4
-1 จะเกิดอะไรขึ้นถ้าสตริงมีช่องว่าง ตัวอย่างเช่นIN="this is first line; this is second line" arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) )จะสร้างอาร์เรย์ของ 8 องค์ประกอบในกรณีนี้ (องค์ประกอบสำหรับการเว้นวรรคแต่ละคำ) แทนที่จะเป็น 2 (องค์ประกอบสำหรับแต่ละการแยกเครื่องหมายทวิภาคกึ่งบรรทัด)
Luca Borrione

3
@Luca ไม่มีสคริปต์ sed สร้างสองบรรทัดอย่างแน่นอน สิ่งที่สร้างรายการหลายรายการสำหรับคุณคือเมื่อคุณใส่ลงในอาร์เรย์ทุบตี (ซึ่งแยกบนพื้นที่สีขาวโดยค่าเริ่มต้น)
Lothar

นั่นคือประเด็น: OP จำเป็นต้องเก็บรายการไว้ในอาร์เรย์เพื่อวนซ้ำตามที่คุณเห็นในการแก้ไข ฉันคิดว่าคำตอบของคุณ (ดี) ที่ไม่ได้กล่าวถึงเพื่อใช้arrIN=( $( echo "$IN" | sed -e 's/;/\n/g' ) )ในการบรรลุเป้าหมายนั้นและเพื่อให้คำแนะนำในการเปลี่ยน IFS เป็นIFS=$'\n'สำหรับผู้ที่มาที่นี่ในอนาคตและจำเป็นต้องแยกสตริงที่มีช่องว่าง (และเพื่อคืนค่ามันในภายหลัง) :)
Luca Borrione

1
@Luca จุดดี อย่างไรก็ตามการกำหนดอาร์เรย์ไม่ได้อยู่ในคำถามเริ่มต้นเมื่อฉันเขียนคำตอบนั้น
lothar

65

สิ่งนี้ยังใช้งานได้:

IN="bla@some.com;john@home.com"
echo ADD1=`echo $IN | cut -d \; -f 1`
echo ADD2=`echo $IN | cut -d \; -f 2`

ระวังวิธีนี้ไม่ถูกต้องเสมอไป ในกรณีที่คุณส่งผ่าน "bla@some.com" เท่านั้นมันจะกำหนดให้กับทั้ง ADD1 และ ADD2


1
คุณสามารถใช้ -s เพื่อหลีกเลี่ยงปัญหาที่กล่าวถึง: superuser.com/questions/896800/… "-f, --fields = LIST เลือกเฉพาะฟิลด์เหล่านี้เท่านั้นและพิมพ์บรรทัดใด ๆ ที่ไม่มีอักขระตัวคั่นนอกจากตัวเลือก -s คือ ระบุ "
fersarr

34

ข้อแตกต่างของคำตอบของ Darronนี่คือสิ่งที่ฉันทำ:

IN="bla@some.com;john@home.com"
read ADDR1 ADDR2 <<<$(IFS=";"; echo $IN)

ฉันคิดว่ามันจะ! เรียกใช้คำสั่งข้างต้นแล้ว "echo $ ADDR1 ... $ ADDR2" และฉันได้รับ "bla@some.com ... john@home.com" เอาท์พุท
nickjb

1
มันใช้งานได้ดีจริง ๆ สำหรับฉัน ... ฉันใช้มันเพื่อวนซ้ำสตริงที่มีเครื่องหมายจุลภาคคั่น, DB, SERVER, PORT data เพื่อใช้ mysqldump
Nick

5
การวินิจฉัย: การIFS=";"มอบหมายนั้นมีอยู่ใน$(...; echo $IN)subshell เท่านั้น นี่คือเหตุผลที่ผู้อ่านบางคน (รวมถึงฉัน) เริ่มแรกคิดว่ามันจะไม่ทำงาน ฉันสันนิษฐานว่าทั้งหมดของ $ IN ได้รับการ slurped ขึ้นโดย ADDR1 แต่ Nickjb นั้นถูกต้อง มันทำงานได้ เหตุผลก็คือecho $INคำสั่งจะแยกวิเคราะห์อาร์กิวเมนต์โดยใช้ค่าปัจจุบันของ $ IFS แต่จากนั้นก็แสดงให้เห็นถึง stdout โดยใช้ตัวคั่นช่องว่างโดยไม่คำนึงถึงการตั้งค่า $ IFS ดังนั้นเอฟเฟกต์สุทธิจึงเหมือนกับว่ามีคนถูกเรียกread ADDR1 ADDR2 <<< "bla@some.com john@home.com"(หมายเหตุอินพุตถูกคั่นด้วยช่องว่างไม่ใช่; - แยก)
dubiousjim

1
สิ่งนี้ล้มเหลวในการเว้นวรรคและการขึ้นบรรทัดใหม่และยังขยาย wildcard *ในecho $INด้วยการขยายตัวแปรที่ไม่ได้กล่าวถึง
ไอแซค

ฉันชอบวิธีนี้มาก คำอธิบายว่าทำไมการทำงานจึงมีประโยชน์มากและทำให้คำตอบโดยรวมดีขึ้น
Michael Gaskill

32

ใน Bash เป็นวิธีพิสูจน์กระสุนซึ่งจะทำงานแม้ว่าตัวแปรของคุณจะมีบรรทัดใหม่:

IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")

ดู:

$ in=$'one;two three;*;there is\na newline\nin this field'
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two three" [2]="*" [3]="there is
a newline
in this field")'

เคล็ดลับในการทำงานนี้คือการใช้-dตัวเลือกของread(ตัวคั่น) กับตัวคั่นที่ว่างเปล่าดังนั้นจึงreadถูกบังคับให้อ่านทุกอย่างที่ป้อน และเรากินreadตรงกับเนื้อหาของตัวแปรที่ไม่มีต่อท้ายบรรทัดใหม่เพื่อขอบคุณin printfโปรดทราบว่าเรากำลังใส่ตัวคั่นไว้printfเพื่อให้แน่ใจว่าสตริงที่ส่งไปreadยังมีตัวคั่นต่อท้าย หากไม่มีก็readจะตัดฟิลด์ว่างที่มีศักยภาพต่อท้าย:

$ in='one;two;three;'    # there's an empty field
$ IFS=';' read -d '' -ra array < <(printf '%s;\0' "$in")
$ declare -p array
declare -a array='([0]="one" [1]="two" [2]="three" [3]="")'

ฟิลด์ว่างต่อท้ายถูกเก็บรักษาไว้


อัพเดทสำหรับBash≥4.4

ตั้งแต่ Bash 4.4 builtin mapfile(aka readarray) รองรับ-dตัวเลือกเพื่อระบุตัวคั่น ดังนั้นอีกวิธีทางบัญญัติคือ:

mapfile -d ';' -t array < <(printf '%s;' "$in")

5
ฉันพบว่ามันเป็นวิธีแก้ปัญหาที่หายากในรายการที่ทำงานได้อย่างถูกต้องกับ\nช่องว่างและ*พร้อมกัน ยังไม่มีลูป; ตัวแปรอาเรย์สามารถเข้าถึงได้ในเชลล์หลังการประมวลผล (ตรงกันข้ามกับคำตอบ upvoted สูงสุด) หมายเหตุin=$'...'มันไม่ทำงานกับเครื่องหมายคำพูดคู่ ฉันคิดว่ามันต้องมีผู้โหวตมากขึ้น
John_West

28

ถ้าคุณไม่ได้ใช้อาร์เรย์:

IFS=';' read ADDR1 ADDR2 <<<$IN

ลองใช้read -r ...เพื่อให้แน่ใจว่าตัวอย่างเช่นอักขระสองตัว "\ t" ในอินพุตสิ้นสุดด้วยอักขระสองตัวเดียวกันในตัวแปรของคุณ (แทนที่จะเป็นอักขระแท็บเดียว)
dubiousjim

-1 สิ่งนี้ไม่ทำงานที่นี่ (ubuntu 12.04) การเพิ่มecho "ADDR1 $ADDR1"\n echo "ADDR2 $ADDR2"ข้อมูลโค้ดของคุณจะถูกส่งออกADDR1 bla@some.com john@home.com\nADDR2(\ n เป็นบรรทัดใหม่)
Luca Borrione

อาจเป็นเพราะข้อผิดพลาดที่เกี่ยวข้องIFSและนี่คือสตริงที่ได้รับการแก้ไขในbash4.3 การอ้างอิง$INควรแก้ไข (ในทางทฤษฎี$INไม่มีการแยกคำหรือกลมกลืนหลังจากที่มันขยายออกไปซึ่งหมายความว่าราคาไม่จำเป็นต้องใช้แม้ใน 4.3 แม้ว่าจะมีข้อผิดพลาดอย่างน้อยหนึ่งข้อที่เหลืออยู่ - รายงานและกำหนดเวลาให้คงที่ ความคิด)
chepner

สิ่งนี้จะหยุดพักหาก $ in มีการขึ้นบรรทัดใหม่แม้ว่าจะมีการเสนอราคา $ IN ก็ตาม และเพิ่มบรรทัดใหม่ที่ตามมา
ไอแซค

ปัญหาเกี่ยวกับสิ่งนี้และวิธีแก้ปัญหาอื่น ๆ ก็คือถือว่ามีองค์ประกอบสองอย่างที่เหมือนกันใน $ IN - หรือคุณยินดีที่จะให้รายการที่สองและที่ตามมาถูกรวมเข้าด้วยกันใน ADDR2 ฉันเข้าใจว่าสิ่งนี้สอดคล้องกับการถาม แต่มันเป็นระเบิดเวลา
สตีเว่นขบขันได้อย่างง่ายดาย

22

โดยไม่ต้องตั้งค่า IFS

หากคุณมีหนึ่งโคลอนคุณสามารถทำได้:

a="foo:bar"
b=${a%:*}
c=${a##*:}

คุณจะได้รับ:

b = foo
c = bar

20

นี่คือ 3 ซับสะอาด:

in="foo@bar;bizz@buzz;fizz@buzz;buzz@woof"
IFS=';' list=($in)
for item in "${list[@]}"; do echo $item; done

ที่IFSคำคั่นคั่นอยู่บนพื้นฐานและ()ถูกนำมาใช้ในการสร้างอาร์เรย์ จากนั้น[@]จะใช้เพื่อส่งคืนแต่ละรายการเป็นคำแยกต่างหาก

หากคุณได้รหัสใด ๆ หลังจากนั้นคุณยังจะต้องเรียกคืนเช่น$IFSunset IFS


5
การใช้$inunquote อนุญาตให้ใช้อักขระตัวแทนเพื่อขยาย
ไอแซค

10

ฟังก์ชั่น Bash / zsh ต่อไปนี้จะแยกอาร์กิวเมนต์แรกของมันบนตัวคั่นที่กำหนดโดยอาร์กิวเมนต์ที่สอง:

split() {
    local string="$1"
    local delimiter="$2"
    if [ -n "$string" ]; then
        local part
        while read -d "$delimiter" part; do
            echo $part
        done <<< "$string"
        echo $part
    fi
}

ตัวอย่างเช่นคำสั่ง

$ split 'a;b;c' ';'

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

a
b
c

ยกตัวอย่างเช่นเอาต์พุตนี้อาจถูกไพพ์ไปยังคำสั่งอื่น ตัวอย่าง:

$ split 'a;b;c' ';' | cat -n
1   a
2   b
3   c

เมื่อเปรียบเทียบกับโซลูชันอื่นที่ให้มาหนึ่งนี้มีข้อดีดังต่อไปนี้:

  • IFSไม่ overriden: เนื่องจากการกำหนดขอบเขตแบบไดนามิกของตัวแปรโลคัลแม้แต่การแทนที่IFSการวนซ้ำทำให้ค่าใหม่รั่วไหลไปยังการเรียกใช้ฟังก์ชันที่ดำเนินการจากภายในลูป

  • ไม่ใช้อาร์เรย์: การอ่านสตริงเข้าในอาร์เรย์โดยใช้readต้องใช้การตั้งค่าสถานะ-aใน Bash และ-Aใน zsh

หากต้องการฟังก์ชั่นอาจถูกใส่ลงในสคริปต์ดังนี้:

#!/usr/bin/env bash

split() {
    # ...
}

split "$@"

ดูเหมือนจะไม่ทำงานกับตัวคั่นที่ยาวเกิน 1 ตัวอักษร: split = $ (แยก "$ content" "ไฟล์: //")
madprops

จริง - จากhelp read:-d delim continue until the first character of DELIM is read, rather than newline
Halle Knast

8

คุณสามารถใช้ awk กับสถานการณ์มากมาย

echo "bla@some.com;john@home.com"|awk -F';' '{printf "%s\n%s\n", $1, $2}'

นอกจากนี้คุณสามารถใช้สิ่งนี้

echo "bla@some.com;john@home.com"|awk -F';' '{print $1,$2}' OFS="\n"

7

มีวิธีที่ง่ายและฉลาดเช่นนี้

echo "add:sfff" | xargs -d: -i  echo {}

แต่คุณต้องใช้ gnu xargs, BSD xargs ไม่รองรับ -d delim ถ้าคุณใช้ apple mac อย่างฉัน คุณสามารถติดตั้ง gnu xargs:

brew install findutils

แล้วก็

echo "add:sfff" | gxargs -d: -i  echo {}


4

มีคำตอบที่ยอดเยี่ยมอยู่ที่นี่ (errator esp.) แต่สำหรับบางสิ่งที่คล้ายคลึงกับการแยกในภาษาอื่น - ซึ่งเป็นสิ่งที่ฉันเอาคำถามเดิมมาหมายถึง - ฉันตัดสินบนนี้:

IN="bla@some.com;john@home.com"
declare -a a="(${IN/;/ })";

ตอนนี้${a[0]}, ${a[1]}ฯลฯ เป็นที่คุณคาดหวัง ใช้${#a[*]}สำหรับจำนวนคำ หรือย้ำแน่นอน:

for i in ${a[*]}; do echo $i; done

โน๊ตสำคัญ:

วิธีนี้ใช้ได้ผลในกรณีที่ไม่มีช่องว่างที่ต้องกังวลซึ่งแก้ไขปัญหาของฉันได้ แต่อาจไม่สามารถแก้ไขปัญหาของคุณได้ ไปกับ$IFSโซลูชันในกรณีนั้น


ไม่ทำงานเมื่อINมีที่อยู่อีเมลมากกว่าสองที่อยู่ โปรดอ้างอิงแนวคิดเดียวกัน (แต่คงที่) ที่คำตอบของ palindrom
olibre

ใช้ดีกว่า${IN//;/ }(double slash) เพื่อให้สามารถใช้งานได้มากกว่าสองค่า ระวังว่าไวด์การ์ด ( *?[) ใด ๆจะถูกขยาย และฟิลด์ว่างต่อท้ายจะถูกยกเลิก
ไอแซค

3
IN="bla@some.com;john@home.com"
IFS=';'
read -a IN_arr <<< "${IN}"
for entry in "${IN_arr[@]}"
do
    echo $entry
done

เอาท์พุต

bla@some.com
john@home.com

ระบบ: Ubuntu 12.04.1


IFS ไม่ได้รับการตั้งค่าในบริบทเฉพาะของreadที่นี่และด้วยเหตุนี้มันสามารถทำให้ส่วนที่เหลือของรหัสถ้ามี
codeforester

2

ถ้าไม่มีที่ว่างทำไมไม่ทำอย่างนี้?

IN="bla@some.com;john@home.com"
arr=(`echo $IN | tr ';' ' '`)

echo ${arr[0]}
echo ${arr[1]}

2

ใช้setบิวด์อินเพื่อโหลด$@อาร์เรย์:

IN="bla@some.com;john@home.com"
IFS=';'; set $IN; IFS=$' \t\n'

จากนั้นให้พรรคเริ่ม:

echo $#
for a; do echo $a; done
ADDR1=$1 ADDR2=$2

ใช้ดีกว่าset -- $INเพื่อหลีกเลี่ยงปัญหาบางอย่างด้วย "$ IN" เริ่มต้นด้วยเส้นประ ถึงกระนั้นการขยายตัวที่ไม่จำเป็นของ$INจะขยายตัวสัญลักษณ์ ( *?[)
ไอแซค

2

bourne-ish สองตัวเลือกที่ไม่ต้องใช้อาร์เรย์ bash:

กรณีที่ 1 : ทำให้มันดีและเรียบง่าย: ใช้ NewLine เป็น Record-Separator ... เช่น

IN="bla@some.com
john@home.com"

while read i; do
  # process "$i" ... eg.
    echo "[email:$i]"
done <<< "$IN"

หมายเหตุ: ในกรณีแรกนี้จะไม่มีการแยกกระบวนการย่อยเพื่อช่วยในการจัดการรายการ

แนวคิด: อาจเป็นการใช้ NL อย่างกว้างขวางภายในและอาจแปลงเป็น RS อื่นเมื่อสร้างผลลัพธ์สุดท้ายจากภายนอกภายนอก

กรณีที่ 2 : การใช้ ";" เป็นตัวคั่นเรคคอร์ด ... เช่น

NL="
" IRS=";" ORS=";"

conv_IRS() {
  exec tr "$1" "$NL"
}

conv_ORS() {
  exec tr "$NL" "$1"
}

IN="bla@some.com;john@home.com"
IN="$(conv_IRS ";" <<< "$IN")"

while read i; do
  # process "$i" ... eg.
    echo -n "[email:$i]$ORS"
done <<< "$IN"

ในทั้งสองกรณีรายการย่อยสามารถสร้างขึ้นภายในลูปจะคงอยู่หลังจากที่ลูปเสร็จสิ้น สิ่งนี้มีประโยชน์เมื่อจัดการรายการในหน่วยความจำแทนที่จะเก็บรายการไว้ในไฟล์ {ps รักษาความสงบและดำเนินการกับ B-)}


2

นอกเหนือจากคำตอบที่ยอดเยี่ยมที่ให้ไว้แล้วหากเป็นเพียงเรื่องของการพิมพ์ข้อมูลที่คุณอาจพิจารณาใช้awk:

awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"

ชุดตัวคั่นฟิลด์นี้ตั้งค่าเพื่อ;ให้สามารถวนรอบฟิลด์ด้วย aforลูปและพิมพ์ตามนั้น

ทดสอบ

$ IN="bla@some.com;john@home.com"
$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "$IN"
> [bla@some.com]
> [john@home.com]

ด้วยอินพุตอื่น:

$ awk -F";" '{for (i=1;i<=NF;i++) printf("> [%s]\n", $i)}' <<< "a;b;c   d;e_;f"
> [a]
> [b]
> [c   d]
> [e_]
> [f]

2

ใน Android shell วิธีการที่เสนอส่วนใหญ่จะไม่ทำงาน:

$ IFS=':' read -ra ADDR <<<"$PATH"                             
/system/bin/sh: can't create temporary file /sqlite_stmt_journals/mksh.EbNoR10629: No such file or directory

งานคืออะไร:

$ for i in ${PATH//:/ }; do echo $i; done
/sbin
/vendor/bin
/system/sbin
/system/bin
/system/xbin

ที่//หมายถึงการทดแทนทั่วโลก


1
ล้มเหลวหากส่วนใดส่วนหนึ่งของ $ PATH มีช่องว่าง (หรือขึ้นบรรทัดใหม่) ขยายสัญลักษณ์แทน (เครื่องหมายดอกจัน * เครื่องหมายคำถาม? และเครื่องหมายวงเล็บปีกกา […])
ไอแซค

2
IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'
set -f
oldifs="$IFS"
IFS=';'; arrayIN=($IN)
IFS="$oldifs"
for i in "${arrayIN[@]}"; do
echo "$i"
done
set +f

เอาท์พุท:

bla@some.com
john@home.com
Charlie Brown <cbrown@acme.com
!"#$%&/()[]{}*? are no problem
simple is beautiful :-)

คำอธิบาย: การมอบหมายอย่างง่ายโดยใช้วงเล็บ () แปลงรายการคั่นด้วยเครื่องหมายอัฒภาคให้เป็นอาร์เรย์หากคุณมี IFS ที่ถูกต้องขณะทำเช่นนั้น Standard FOR loop จัดการแต่ละไอเท็มในอาร์เรย์นั้นตามปกติ โปรดสังเกตว่ารายการที่ให้ไว้สำหรับตัวแปร IN จะต้องอยู่ในเครื่องหมาย "ยาก" กล่าวคือมีขีดเดียว

ต้องบันทึกและกู้คืน IFS เนื่องจาก Bash ไม่ได้ทำการกำหนดเช่นเดียวกับคำสั่ง วิธีแก้ปัญหาอื่นคือการตัดการมอบหมายภายในฟังก์ชันและเรียกใช้ฟังก์ชันนั้นด้วย IFS ที่ปรับเปลี่ยน ในกรณีนั้นไม่จำเป็นต้องทำการบันทึก / เรียกคืน IFS แยกต่างหาก ขอบคุณสำหรับ "Bize" ที่ชี้ให้เห็น


!"#$%&/()[]{}*? are no problemดี ... ไม่มาก: []*?เป็นตัวละครแบบกลม ดังนั้นสิ่งที่เกี่ยวกับการสร้างไดเรกทอรีและไฟล์นี้: `mkdir '!" # $% &'; touch '! "# $% & / () [] {} คุณมีฮ่าฮ่าฮ่าฮ่า - ไม่มีปัญหา' และใช้คำสั่งของคุณ? เรียบง่ายอาจจะสวยงาม แต่เมื่อมันพังมันก็แตก
gniourf_gniourf

@gniourf_gniourf สตริงถูกเก็บไว้ในตัวแปร โปรดดูคำถามเดิม
ajaaskel

1
@ajaaskel คุณไม่เข้าใจความคิดเห็นของฉัน mkdir '!"#$%&'; touch '!"#$%&/()[]{} got you hahahaha - are no problem'ไปในไดเรกทอรีรอยขีดข่วนและออกคำสั่งเหล่านี้: พวกเขาจะสร้างไดเรกทอรีและไฟล์ด้วยชื่อที่ดูแปลก ๆ ฉันต้องยอมรับ จากนั้นเรียกใช้คำสั่งของคุณด้วยแน่นอนคุณให้:IN IN='bla@some.com;john@home.com;Charlie Brown <cbrown@acme.com;!"#$%&/()[]{}*? are no problem;simple is beautiful :-)'คุณจะเห็นว่าคุณจะไม่ได้รับผลลัพธ์ที่คุณคาดหวัง เพราะคุณใช้วิธีการภายใต้ชื่อพา ธ เพื่อขยายสตริงของคุณ
gniourf_gniourf

นี้คือการแสดงให้เห็นว่าตัวละคร*, ?, [...]และแม้กระทั่งถ้าextglobมีการตั้งค่า!(...), @(...), ?(...), +(...) มีปัญหากับวิธีนี้
gniourf_gniourf

1
@gniourf_gniourf ขอบคุณสำหรับความคิดเห็นโดยละเอียดเกี่ยวกับ globbing ฉันปรับรหัสให้มีการปิดลง จุดของฉันเป็นเพียงเพื่อแสดงให้เห็นว่าการมอบหมายที่ค่อนข้างง่ายสามารถทำงานแยกได้
ajaaskel

1

โอเคพวก!

นี่คือคำตอบของฉัน!

DELIMITER_VAL='='

read -d '' F_ABOUT_DISTRO_R <<"EOF"
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.4 LTS"
NAME="Ubuntu"
VERSION="14.04.4 LTS, Trusty Tahr"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 14.04.4 LTS"
VERSION_ID="14.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
EOF

SPLIT_NOW=$(awk -F$DELIMITER_VAL '{for(i=1;i<=NF;i++){printf "%s\n", $i}}' <<<"${F_ABOUT_DISTRO_R}")
while read -r line; do
   SPLIT+=("$line")
done <<< "$SPLIT_NOW"
for i in "${SPLIT[@]}"; do
    echo "$i"
done

ทำไมวิธีนี้จึงเป็น "ดีที่สุด" สำหรับฉัน

เนื่องจากเหตุผลสองประการ:

  1. คุณไม่จำเป็นต้องหลบหนีจากตัวคั่น
  2. คุณจะไม่ได้มีปัญหากับช่องว่าง ค่าจะถูกคั่นอย่างเหมาะสมในอาร์เรย์!

[] 's


FYI /etc/os-releaseและ/etc/lsb-releaseมีไว้เพื่อเป็นแหล่งข้อมูลและไม่ได้แยกวิเคราะห์ ดังนั้นวิธีการของคุณผิดจริงๆ ยิ่งกว่านั้นคุณยังไม่ค่อยตอบคำถามเกี่ยวกับการแทรกสตริงลงในตัวคั่น
gniourf_gniourf

0

หนึ่งซับเพื่อแยกสตริงที่คั่นด้วย ';' เป็นอาร์เรย์คือ:

IN="bla@some.com;john@home.com"
ADDRS=( $(IFS=";" echo "$IN") )
echo ${ADDRS[0]}
echo ${ADDRS[1]}

นี่เป็นเพียงการตั้งค่า IFS ใน subshell ดังนั้นคุณไม่ต้องกังวลเกี่ยวกับการบันทึกและการคืนค่า


-1 สิ่งนี้ไม่ทำงานที่นี่ (ubuntu 12.04) มันพิมพ์เฉพาะเสียงสะท้อนแรกที่มีค่า $ IN ทั้งหมดในขณะที่สองนั้นว่างเปล่า คุณสามารถดูได้ถ้าคุณใส่ echo "0:" $ {ADDRS [0]} \ n echo "1:" $ {ADDRS [1]} เอาท์พุทคือ0: bla@some.com;john@home.com\n 1:(\ n เป็นบรรทัดใหม่)
Luca Borrione

1
โปรดดูคำตอบของ nickjb ที่เป็นทางเลือกในการทำงานกับความคิดนี้ stackoverflow.com/a/6583589/1032370
Luca Borrione

1
-1, 1. IFS ไม่ได้ถูกตั้งค่าไว้ใน subshell นั้น (มันถูกส่งไปยังสภาพแวดล้อมของ "echo" ซึ่งเป็น builtin ดังนั้นจึงไม่มีอะไรเกิดขึ้น) 2. $INมีการเสนอราคาดังนั้นจึงไม่ขึ้นกับการแยก IFS 3. การทดแทนกระบวนการถูกแบ่งโดยช่องว่าง แต่อาจทำให้ข้อมูลต้นฉบับเสียหายได้
คะแนน _ ต่ำกว่า

0

อาจไม่ใช่โซลูชันที่หรูหราที่สุด แต่ใช้ได้กับ*และช่องว่าง:

IN="bla@so me.com;*;john@home.com"
for i in `delims=${IN//[^;]}; seq 1 $((${#delims} + 1))`
do
   echo "> [`echo $IN | cut -d';' -f$i`]"
done

เอาท์พุท

> [bla@so me.com]
> [*]
> [john@home.com]

ตัวอย่างอื่น ๆ (ตัวคั่นที่จุดเริ่มต้นและจุดสิ้นสุด):

IN=";bla@so me.com;*;john@home.com;"
> []
> [bla@so me.com]
> [*]
> [john@home.com]
> []

โดยพื้นฐานแล้วมันจะลบตัวละครทุกตัวที่นอกเหนือจาก;การสร้างdelimsเช่น ;;;. จากนั้นก็จะไม่forห่วงจาก1การนับโดยnumber-of-delimiters ${#delims}ขั้นตอนสุดท้ายคือการได้อย่างปลอดภัยได้รับ$iส่วน TH cutใช้

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