ฉันจะเข้าร่วมองค์ประกอบของอาร์เรย์ใน Bash ได้อย่างไร


416

หากฉันมีอาร์เรย์เช่นนี้ใน Bash:

FOO=( a b c )

ฉันจะเข้าร่วมองค์ประกอบด้วยเครื่องหมายจุลภาคได้อย่างไร a,b,cยกตัวอย่างเช่นการผลิต

คำตอบ:


571

โซลูชันการเขียนซ้ำโดย Pascal Pilz เป็นฟังก์ชันใน 100% pure Bash (ไม่มีคำสั่งภายนอก):

function join_by { local IFS="$1"; shift; echo "$*"; }

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

join_by , a "b c" d #a,b c,d
join_by / var local tmp #var/local/tmp
join_by , "${FOO[@]}" #a,b,c

หรือเราสามารถใช้ printf เพื่อรองรับตัวคั่นหลายตัวโดยใช้แนวคิดโดย @gniourf_gniourf

function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }

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

join_by , a b c #a,b,c
join_by ' , ' a b c #a , b , c
join_by ')|(' a b c #a)|(b)|(c
join_by ' %s ' a b c #a %s b %s c
join_by $'\n' a b c #a<newline>b<newline>c
join_by - a b c #a-b-c
join_by '\' a b c #a\b\c

9
ใช้สิ่งนี้สำหรับตัวคั่นหลายตัว: ฟังก์ชั่นเข้าร่วม {perl -e '$ s = shift @ARGV; พิมพ์เข้าร่วม ($ s, @ARGV); ' "$ @"; } เข้าร่วม ',' abc # a, b, c
Daniel Patru

4
@datat ต่อไปเพื่อให้ทุบตีบริสุทธิ์หรือไม่
CMCDragonkai

4
@puchu สิ่งที่ใช้ไม่ได้คือตัวแยกหลายอักขระ การพูดว่า "ช่องว่างไม่ทำงาน" ทำให้ฟังดูเหมือนเข้าร่วมกับช่องว่างไม่ทำงาน มันทำ
Eric

6
สิ่งนี้จะส่งเสริมการวางไข่ subshells ถ้าเก็บผลลัพธ์ไปยังตัวแปร ใช้konsoleboxรูปแบบ :) หรือfunction join { local IFS=$1; __="${*:2}"; } function join { IFS=$1 eval '__="${*:2}"'; }จากนั้นใช้__หลังจาก ใช่ฉันเป็นคนหนึ่งที่ส่งเสริมการใช้__เป็นตัวแปรผลลัพธ์;) (และตัวแปรการทำซ้ำทั่วไปหรือตัวแปรชั่วคราว) หากแนวคิดได้รับความนิยมจากเว็บไซต์วิกิพีเดียทุบตีพวกเขาคัดลอกฉัน :)
konsolebox

6
อย่าใส่การขยายตัวในรูปแบบของตัวระบุ$d printfคุณคิดว่าคุณปลอดภัยเพราะคุณ“ หลบหนี” %แต่มีข้อแม้อื่น ๆ : เมื่อตัวคั่นประกอบด้วยเครื่องหมายแบ็กสแลช (เช่น\n) หรือเมื่อตัวคั่นเริ่มต้นด้วยเครื่องหมายยัติภังค์ แน่นอนว่าคุณสามารถแก้ไขสิ่งเหล่านี้ได้ (แทนที่แบ็กสแลชโดยใช้แบ็กสแลชคู่และใช้printf -- "$d%s") แต่ในบางจุดคุณจะรู้สึกว่าคุณกำลังต่อสู้กับเชลล์แทนที่จะทำงานกับมัน นั่นเป็นเหตุผลว่าทำไมในคำตอบของฉันด้านล่างฉันได้เตรียมตัวคั่นกับข้อกำหนดที่จะเข้าร่วม
gniourf_gniourf

206

อีกวิธีหนึ่ง:

#!/bin/bash
foo=('foo bar' 'foo baz' 'bar baz')
bar=$(printf ",%s" "${foo[@]}")
bar=${bar:1}

echo $bar

แก้ไข: เหมือนกัน แต่สำหรับตัวคั่นความยาวตัวแปรหลายตัว:

#!/bin/bash
separator=")|(" # e.g. constructing regex, pray it does not contain %s
foo=('foo bar' 'foo baz' 'bar baz')
regex="$( printf "${separator}%s" "${foo[@]}" )"
regex="${regex:${#separator}}" # remove leading separator
echo "${regex}"
# Prints: foo bar)|(foo baz)|(bar baz

7
+1 printf -v bar ",%s" "${foo[@]}"สิ่งที่เกี่ยวกับ มันforkน้อยกว่า (จริง ๆclone) printf -v bar ",%s" $(<infile)แม้มันเป็นฟอร์กอ่านไฟล์:
TrueY

14
แทนการสวดมนต์$separatorไม่ได้มี%sหรือเช่นคุณสามารถทำให้คุณแข็งแกร่ง:printf printf "%s%s" "$separator" "${foo[@]}"
musiphil

5
@musiphil ผิด จากชายคนทุบตี: "รูปแบบจะถูกนำกลับมาใช้ตามความจำเป็นในการบริโภคทั้งหมดของการขัดแย้งการใช้สองตัวยึดรูปแบบเหมือนใน. printf "%s%s"จะใช้คั่นในกรณีแรกการตั้งค่าเฉพาะของการส่งออกและจากนั้นก็เชื่อมส่วนที่เหลือของการขัดแย้ง.
AnyDev

3
@AndrDevEK: ขอบคุณที่เข้าใจผิด printf "%s" "${foo[@]/#/$separator}"แต่ผมจะแนะนำสิ่งที่ต้องการ
musiphil

2
@ musiphil ขอบคุณ ใช่ จากนั้นก็จะกลายเป็น printf IFS=; regex="${foo[*]/#/$separator}"ซ้ำซ้อนและสายที่สามารถจะลดลงไป ณ จุดนี้สิ่งนี้กลายเป็นคำตอบของ gniourf_gniourf ซึ่ง IMO นั้นสะอาดกว่าตั้งแต่เริ่มต้นนั่นคือการใช้ฟังก์ชั่นเพื่อ จำกัด ขอบเขตการเปลี่ยนแปลงของ IFS และ temp vars
AnyDev

145
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d

3
ไม่จำเป็นต้องใช้อัญประกาศคู่นอกและเครื่องหมายอัญประกาศคู่รอบโคลอน เฉพาะเครื่องหมายคำพูดคู่ด้านในเท่านั้นที่จำเป็น:bar=$( IFS=, ; echo "${foo[*]}" )
เซเว่น

8
+1 สำหรับโซลูชันที่กะทัดรัดที่สุดซึ่งไม่ต้องการลูปซึ่งไม่ต้องการคำสั่งภายนอกและไม่กำหนดข้อ จำกัด เพิ่มเติมเกี่ยวกับชุดอักขระของอาร์กิวเมนต์
เซเว่น

22
ฉันชอบวิธีแก้ปัญหา แต่ใช้งานได้เฉพาะถ้า IFS เป็นตัวละครตัวเดียว
Jayen

8
ความคิดว่าทำไมไม่ทำงานถ้าใช้@แทน*ในขณะที่$(IFS=, ; echo "${foo[@]}")? ฉันจะเห็นว่า*ช่องว่างที่เก็บรักษาไว้ในองค์ประกอบแล้วไม่แน่ใจว่าเพราะ@ปกติแล้วเป็นสิ่งที่จำเป็น
haridsv

10
ฉันพบคำตอบสำหรับคำถามของฉันเองด้านบน *คำตอบก็คือไอเอฟเอได้รับการยอมรับเฉพาะสำหรับ ในหน้า bash man ให้ค้นหา "พารามิเตอร์พิเศษ" แล้วมองหาคำอธิบายถัดจาก*:
haridsv

66

อาจจะเช่น

SAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"

echo "$FOOJOIN"

3
หากคุณทำเช่นนั้นก็คิดว่า IFS- เป็นตัวแปร คุณต้องทำecho "-${IFS}-"(วงเล็บปีกกาแยกเส้นประออกจากชื่อตัวแปร)
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

1
ยังคงมีผลเหมือนกัน (ฉันเพียงแค่ใส่ขีดกลางในการที่จะแสดงให้เห็นถึงจุด ... echo $IFSจะเป็นสิ่งเดียวกัน.
เดวิด Wolever

41
ที่กล่าวว่ายังคงใช้งานได้ ... ดังนั้นเหมือนกับสิ่งส่วนใหญ่กับ Bash ฉันจะแกล้งทำเป็นว่าฉันเข้าใจและเข้ากับชีวิตของฉัน
David Wolever

2
A "-" ไม่ใช่อักขระที่ถูกต้องสำหรับชื่อตัวแปรดังนั้นเชลล์ทำสิ่งที่ถูกต้องเมื่อคุณใช้ $ IFS- คุณไม่ต้องการ $ {IFS} - (bash, ksh, sh และ zsh ใน linux และ solaris ยังเห็นด้วย)
Idelic

2
@David ความแตกต่างระหว่างเสียงสะท้อนของคุณและเดนนิสคือเขาได้ใช้การอ้างสองครั้ง เนื้อหาของ IFS นั้นจะใช้ 'ในอินพุต' เป็นการประกาศตัวคั่นคำดังนั้นคุณจะได้รับบรรทัดว่างโดยไม่มีเครื่องหมายคำพูด
มาร์ตินเคลย์ตัน

30

น่าแปลกที่โซลูชันของฉันยังไม่ได้รับ :) นี่เป็นวิธีที่ง่ายที่สุดสำหรับฉัน ไม่ต้องการฟังก์ชั่น:

IFS=, eval 'joined="${foo[*]}"'

หมายเหตุ: วิธีแก้ปัญหานี้ตรวจพบว่าทำงานได้ดีในโหมดที่ไม่ใช่ POSIX ในโหมด POSIXองค์ประกอบจะยังคงเข้าร่วมอย่างถูกต้อง แต่IFS=,กลายเป็นแบบถาวร


น่าเสียดายที่ใช้ได้เฉพาะกับตัวคั่นอักขระเดียว
maoizm

24

นี่คือฟังก์ชั่น pure Bash 100% ที่ทำงาน:

join() {
    # $1 is return variable name
    # $2 is sep
    # $3... are the elements to join
    local retname=$1 sep=$2 ret=$3
    shift 3 || shift $(($#))
    printf -v "$retname" "%s" "$ret${@/#/$sep}"
}

ดู:

$ a=( one two "three three" four five )
$ join joineda " and " "${a[@]}"
$ echo "$joineda"
one and two and three three and four and five
$ join joinedb randomsep "only one element"
$ echo "$joinedb"
only one element
$ join joinedc randomsep
$ echo "$joinedc"

$ a=( $' stuff with\nnewlines\n' $'and trailing newlines\n\n' )
$ join joineda $'a sep with\nnewlines\n' "${a[@]}"
$ echo "$joineda"
 stuff with
newlines
a sep with
newlines
and trailing newlines


$

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

join() {
    # $1 is sep
    # $2... are the elements to join
    # return is in global variable join_ret
    local sep=$1 IFS=
    join_ret=$2
    shift 2 || shift $(($#))
    join_ret+="${*/#/$sep}"
}

1
ทางออกสุดท้ายของคุณดีมาก แต่สามารถทำให้สะอาดขึ้นได้ด้วยการสร้างjoin_retตัวแปรเฉพาะที่จากนั้นจึงสะท้อนออกมาในตอนท้าย วิธีนี้ทำให้สามารถใช้ join () ในวิธีการเขียนสคริปต์เชลล์ปกติเช่น$(join ":" one two three)และไม่จำเป็นต้องใช้ตัวแปรโกลบอล
James Sneeringer

1
@JamesSneeringer ฉันตั้งใจจะใช้การออกแบบนี้เพื่อหลีกเลี่ยงการใช้ subshells ในการเขียนสคริปต์เชลล์ซึ่งแตกต่างจากภาษาอื่น ๆ หลายตัวแปรทั่วโลกที่ใช้วิธีการที่ไม่จำเป็นต้องเป็นสิ่งที่ไม่ดี; โดยเฉพาะอย่างยิ่งถ้าพวกเขาอยู่ที่นี่เพื่อช่วยหลีกเลี่ยง subshells ยิ่งกว่านั้น$(...)จดจ้องขึ้นบรรทัดใหม่ ดังนั้นหากฟิลด์สุดท้ายของอาเรย์มีการขึ้นบรรทัดใหม่ต่อท้ายสิ่งเหล่านี้จะถูกตัดแต่ง (ดูตัวอย่างที่ไม่ได้ถูกตัดแต่งด้วยการออกแบบของฉัน)
gniourf_gniourf

ใช้ได้กับตัวแยกหลายตัวซึ่งทำให้ฉันมีความสุข ^ _ ^
spiffytech

ในการพูดถึง "ทำไมคุณไม่ชอบ printf -v?": ใน Bash ตัวแปรในตัวเครื่องนั้นไม่ได้มีฟังก์ชั่นการทำงานจริงดังนั้นคุณสามารถทำสิ่งนี้ได้ (ฟังก์ชั่นการโทร f1 พร้อมตัวแปรโลคอล x ซึ่งจะเรียกฟังก์ชั่น f2 ซึ่งแก้ไข x - ซึ่งได้รับการประกาศในขอบเขตของ f1) แต่มันก็ไม่จริงว่าตัวแปรในท้องถิ่นควรจะทำงานอย่างไร หากตัวแปรโลคัลเป็นโลคัลจริงๆ (หรือสันนิษฐานว่าเป็นอินสแตนซ์ในสคริปต์ที่ต้องทำงานกับทั้ง bash และ ksh) ดังนั้นมันจะทำให้เกิดปัญหากับทั้งหมดนี้ "คืนค่าโดยการเก็บไว้ในตัวแปรที่มีชื่อโครงการนี้"
tetsujin

15

สิ่งนี้ไม่ได้แตกต่างจากโซลูชันที่มีอยู่ทั้งหมด แต่หลีกเลี่ยงการใช้ฟังก์ชั่นแยกต่างหากไม่ได้แก้ไขIFSในเชลล์พาเรนต์และทั้งหมดในบรรทัดเดียว:

arr=(a b c)
printf '%s\n' "$(IFS=,; printf '%s' "${arr[*]}")"

ที่เกิดขึ้นใน

a,b,c

ข้อ จำกัด : ตัวคั่นต้องไม่ยาวเกินหนึ่งอักขระ


13

ใช้ไม่มีคำสั่งภายนอก:

$ FOO=( a b c )     # initialize the array
$ BAR=${FOO[@]}     # create a space delimited string from array
$ BAZ=${BAR// /,}   # use parameter expansion to substitute spaces with comma
$ echo $BAZ
a,b,c

คำเตือนสันนิษฐานว่าองค์ประกอบไม่มีช่องว่าง


4
หากคุณไม่ต้องการใช้ตัวแปรกลางก็สามารถทำได้สั้นลง:echo ${FOO[@]} | tr ' ' ','
jesjimher

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

12

ฉันจะสะท้อนอาร์เรย์เป็นสตริงแล้วแปลงช่องว่างเป็นตัวดึงข้อมูลบรรทัดแล้วใช้pasteเพื่อรวมทุกอย่างในหนึ่งบรรทัดดังนี้:

tr " " "\n" <<< "$FOO" | paste -sd , -

ผล:

a,b,c

นี่เป็นสิ่งที่เร็วและสะอาดสำหรับฉัน!


$FOOเป็นเพียงองค์ประกอบแรกของอาเรย์ นอกจากนี้ยังแบ่งองค์ประกอบอาร์เรย์ที่มีช่องว่าง
Benjamin W.

9

ด้วยการนำกลับมาใช้ใหม่ของ @ ไม่สำคัญสำหรับการแก้ปัญหา แต่ด้วยคำสั่งเดียวโดยหลีกเลี่ยงสเตจย่อย $ {: 1} และต้องการตัวแปรตัวกลาง

echo $(printf "%s," "${LIST[@]}" | cut -d "," -f 1-${#LIST[@]} )

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


7
s=$(IFS=, eval 'echo "${FOO[*]}"')

8
คุณควรจะตอบคำถามของคุณ
joce

หนึ่งที่ดีที่สุด ขอบคุณ !!
เตอร์แพน gz

4
ฉันหวังว่าฉันสามารถลงคะแนนคำตอบนี้เพราะเปิดช่องโหว่และเพราะจะทำลายช่องว่างในองค์ประกอบ
eel ghEEz

1
@bxm แน่นอนดูเหมือนว่าจะรักษาช่องว่างและไม่อนุญาตให้หลบหนีจากบริบทข้อโต้แย้ง echo ฉันคิดว่าการเพิ่ม@Qสามารถหลีกเลี่ยงค่าการเข้าร่วมจากการตีความผิดเมื่อพวกเขามีผู้เข้าร่วมในพวกเขา: foo=("a ," "b ' ' c" "' 'd e" "f " ";" "ls -latr"); s=$(IFS=, eval 'echo "${foo[*]@Q}"'); echo "${s}"ผลลัพธ์'a ,','b '\'' '\'' c',''\'' '\''d e','f ',';','ls -latr '
ปลาไหล ghEEz

1
หลีกเลี่ยงการแก้ปัญหาที่ใช้ประโยชน์จาก subshells เว้นแต่จำเป็น
konsolebox

5

โซลูชัน printf ที่ยอมรับตัวคั่นความยาวเท่าใดก็ได้ (ตาม @ ไม่สำคัญกับคำตอบ)

#/!bin/bash
foo=('foo bar' 'foo baz' 'bar baz')

sep=',' # can be of any length
bar=$(printf "${sep}%s" "${foo[@]}")
bar=${bar:${#sep}}

echo $bar

สิ่งนี้สร้างผลลัพธ์ด้วยเครื่องหมายจุลภาคชั้นนำ
Mark Renouf

แถบสุดท้าย = $ {bar: $ {# sep}} จะลบตัวคั่นออก ฉันเพิ่งคัดลอกและวางใน bash shell และใช้งานได้ คุณใช้เปลือกอะไร
Riccardo Galli

2
ตัวprintf ระบุรูปแบบใด ๆ(เช่น%sโดยไม่ได้ตั้งใจ$sepจะทำให้เกิดปัญหา
Peter.O

sep${sep//\%/%%}สามารถปรุงแต่งด้วย ฉันชอบทางออกของคุณดีกว่า${bar#${sep}}หรือ${bar%${sep}}(ทางเลือก) นี้เป็นสิ่งที่ดีถ้าแปลงเป็นฟังก์ชั่นที่ร้านค้าส่งผลให้ตัวแปรทั่วไปเช่น__และไม่echoมัน
konsolebox

function join_by { printf -v __ "${1//\%/%%}%s" "${@:2}"; __=${__:${#1}}; }
konsolebox

4
$ set a 'b c' d

$ history -p "$@" | paste -sd,
a,b c,d

สิ่งนี้ควรอยู่ด้านบนสุด
Eric Walker

6
นี้ไม่ควรเป็นที่ด้านบน: สิ่งที่ถ้าHISTSIZE=0?
har-wradim

@ har-wradim, เคล็ดลับpaste -sd,ไม่ได้เกี่ยวกับการใช้ประวัติศาสตร์
Veda

@Veda ไม่มันเกี่ยวกับการใช้ชุดค่าผสมและจะไม่ทำงานถ้าHISTSIZE=0ลองใช้ดู
har-wradim

4

เวอร์ชันสั้นกว่าของคำตอบยอดนิยม:

joinStrings() { local a=("${@:3}"); printf "%s" "$2${a[@]/#/$1}"; }

การใช้งาน:

joinStrings "$myDelimiter" "${myArray[@]}"

1
รุ่นที่ยาวขึ้น แต่ไม่จำเป็นต้องทำสำเนาชิ้นส่วนของอาร์กิวเมนต์ไปยังตัวแปรอาร์เรย์:join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '%s' "${@/#/$d}"; }
Rockallite

Yet Another Version: join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '$d%s' "${@}"; } ใช้งานได้กับการใช้งาน: join_strings 'delim' "${array[@]}"หรือjoin_strings 'delim' ${array[@]}
ยกเลิกการอ้างถึง

4

รวมที่ดีที่สุดของโลกทั้งหมดจนถึงกับแนวคิดต่อไปนี้

# join with separator
join_ws()  { local IFS=; local s="${*/#/$1}"; echo "${s#"$1$1$1"}"; }

ผลงานชิ้นเอกชิ้นนี้คือ

  • ทุบตีบริสุทธิ์ 100% (การขยายพารามิเตอร์ด้วย IFS ยกเลิกการตั้งค่าชั่วคราวไม่มีการโทรภายนอกไม่มีการพิมพ์ ...
  • กะทัดรัดสมบูรณ์และไร้ที่ติ (ทำงานกับตัว จำกัด เดี่ยวและหลายตัวทำงานกับตัว จำกัด ที่มีพื้นที่สีขาวตัวแบ่งบรรทัดและอักขระพิเศษของเชลล์อื่น ๆ ทำงานร่วมกับตัวคั่นที่ว่างเปล่า)
  • มีประสิทธิภาพ (ไม่มี subshell ไม่มีการคัดลอกอาเรย์)
  • เรียบง่ายและโง่และในระดับหนึ่งสวยงามและให้คำแนะนำเช่นกัน

ตัวอย่าง:

$ join_ws , a b c
a,b,c
$ join_ws '' a b c
abc
$ join_ws $'\n' a b c
a
b
c
$ join_ws ' \/ ' A B C
A \/ B \/ C

1
ไม่ว่าดี: อย่างน้อย 2 ปัญหา: 1 join_ws ,(มีข้อโต้แย้งไม่ได้) ,,อย่างไม่ยุติธรรมเอาท์พุท 2. join_ws , -eส่งผลอะไรผิดพลาด (นั่นเป็นเพราะคุณใช้ผิดechoแทนที่จะใช้printf) จริง ๆ แล้วฉันไม่รู้ว่าทำไมคุณโฆษณาถึงการใช้echoแทนprintf: echoจะเสียชื่อเสียงและprintfเป็นตัวที่แข็งแกร่ง
gniourf_gniourf

1

ตอนนี้ฉันกำลังใช้:

TO_IGNORE=(
    E201 # Whitespace after '('
    E301 # Expected N blank lines, found M
    E303 # Too many blank lines (pep8 gets confused by comments)
)
ARGS="--ignore `echo ${TO_IGNORE[@]} | tr ' ' ','`"

ซึ่งใช้งานได้ แต่ (ในกรณีทั่วไป) จะแตกหักอย่างน่ากลัวหากองค์ประกอบอาเรย์มีช่องว่างอยู่ในนั้น

(สำหรับผู้ที่สนใจนี่เป็นสคริปต์ตัวหุ้มรอบpep8.py )


คุณจะได้รับค่าอาร์เรย์จากที่ไหน หากคุณฮาร์ดโค้ดเหมือนอย่างนั้นทำไมไม่เพียงแค่ foo = "a, b, c"
ghostdog74

ในกรณีนี้จริง ๆ แล้วฉันเขียนโค้ดยาก ๆ แต่ฉันต้องการใส่มันเข้าไปในอาร์เรย์ ฉันได้อัปเดตคำตอบเพื่อแสดงความหมายของคุณแล้ว
David Wolever

ARGS="--ignore $(echo "${TO_IGNORE[@]}" | tr ' ' ',')"สมมติว่าคุณเป็นจริงโดยใช้ทุบตีนี้อาจจะทำงานได้ดีขึ้น: ผู้ประกอบการ$()มีประสิทธิภาพมากกว่า backtics (อนุญาตการซ้อน$()และ"") การห่อ${TO_IGNORE[@]}ด้วยเครื่องหมายคำพูดคู่ควรช่วยด้วย
kevinarpe


1

ใช้ Perl สำหรับตัวคั่นหลายตัว:

function join {
   perl -e '$s = shift @ARGV; print join($s, @ARGV);' "$@"; 
}

join ', ' a b c # a, b, c

หรือในหนึ่งบรรทัด:

perl -le 'print join(shift, @ARGV);' ', ' 1 2 3
1, 2, 3

ใช้งานได้สำหรับฉันแม้ว่าjoinชื่อจะขัดแย้งกับอึบ้างOS Xฉันจะเรียกมันว่าconjoinedหรืออาจจะjackie_joyner_kersee?
Alex Gray

1

ขอบคุณ @gniourf_gniourf สำหรับความคิดเห็นโดยละเอียดเกี่ยวกับการรวมกันของโลกที่ดีที่สุด ขออภัยที่โพสต์โค้ดไม่ได้ออกแบบและทดสอบอย่างละเอียด นี่คือความพยายามที่ดีกว่า

# join with separator
join_ws() { local d=$1 s=$2; shift 2 && printf %s "$s${@/#/$d}"; }

ความงามตามแนวคิดนี้คือ

  • (ยังคง) ทุบตีบริสุทธิ์ 100% (ขอบคุณสำหรับการชี้ให้เห็นอย่างชัดเจนว่า printf เป็น builtin เช่นกันฉันไม่ทราบเกี่ยวกับเรื่องนี้มาก่อน ... )
  • ทำงานร่วมกับตัวคั่นหลายตัว
  • มีขนาดกะทัดรัดและสมบูรณ์มากขึ้นและในครั้งนี้คิดอย่างรอบคอบเกี่ยวกับการทดสอบความเค้นและระยะยาวกับสตริงย่อยแบบสุ่มจากเชลล์สคริปอื่น ๆ ครอบคลุมการใช้อักขระพิเศษของเชลล์หรืออักขระควบคุมหรือไม่มีอักขระในทั้งตัวคั่นและ / หรือพารามิเตอร์ และมุมกรณีและ quibbles อื่น ๆ ที่ไม่มีข้อโต้แย้งเลย ไม่ได้รับประกันว่าจะไม่มีข้อบกพร่องอีกต่อไป แต่มันจะเป็นความท้าทายที่ยากขึ้นในการค้นหา BTW แม้แต่คำตอบที่ได้รับคะแนนสูงสุดในปัจจุบันและที่เกี่ยวข้องกับความทุกข์ทรมานจากสิ่งต่าง ๆ เช่น -e bug ...

ตัวอย่างเพิ่มเติม:

$ join_ws '' a b c
abc
$ join_ws ':' {1,7}{A..C}
1A:1B:1C:7A:7B:7C
$ join_ws -e -e
-e
$ join_ws $'\033[F' $'\n\n\n'  1.  2.  3.  $'\n\n\n\n'
3.
2.
1.
$ join_ws $ 
$

1

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

foo="aa bb cc dd"
bar=`for i in $foo; do printf ",'%s'" $i; done`
bar=${bar:1}
echo $bar
    'aa','bb','cc','dd'

ตัวอย่างเช่นกรณีการใช้งานของฉันคือมีการส่งผ่านสตริงบางส่วนในเชลล์สคริปต์ของฉันและฉันจำเป็นต้องใช้สิ่งนี้เพื่อรันบนเคียวรี SQL:

./my_script "aa bb cc dd"

ใน my_script ฉันต้องทำ "SELECT * จากตาราง WHERE ชื่อ IN ('aa', 'bb', 'cc', 'dd') จากนั้นคำสั่งด้านบนจะมีประโยชน์


คุณสามารถใช้printf -v bar ...แทนที่จะต้องเรียกใช้ลูป printf ใน subshell และจับเอาท์พุท
codeforester

โซลูชัน upvoted แฟนซีทั้งหมดข้างต้นไม่ทำงาน แต่อันที่จริงแล้วของคุณทำเพื่อฉัน (Y)
ishandutta2007

1

นี่คือเชลล์ที่รองรับ POSIX ส่วนใหญ่รองรับ:

join_by() {
    # Usage:  join_by "||" a b c d
    local arg arr=() sep="$1"
    shift
    for arg in "$@"; do
        if [ 0 -lt "${#arr[@]}" ]; then
            arr+=("${sep}")
        fi
        arr+=("${arg}") || break
    done
    printf "%s" "${arr[@]}"
}

มันเป็นรหัส Bash ที่ดี แต่ POSIX ไม่มีอาร์เรย์ (หรือlocal) เลย
Anders Kaseorg

@Anders: ใช่ฉันได้เรียนรู้วิธีการที่ยากมากเมื่อเร็ว ๆ นี้ :( ฉันจะทิ้งมันไว้ตอนนี้เนื่องจากเปลือกหอยที่รองรับ POSIX ส่วนใหญ่ดูเหมือนจะสนับสนุนอาร์เรย์
user541686

1

การใช้ตัวแปรทางอ้อมเพื่ออ้างอิงโดยตรงกับอาเรย์ก็ใช้งานได้เช่นกัน การอ้างอิงที่มีชื่อยังสามารถใช้ได้ แต่ข้อมูลอ้างอิงนั้นมีให้ใน 4.3 เท่านั้น

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

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

function join_by_ref {
    __=
    local __r=$1[@] __s=${2-' '}
    printf -v __ "${__s//\%/%%}%s" "${!__r}"
    __=${__:${#__s}}
}

array=(1 2 3 4)

join_by_ref array
echo "$__" # Prints '1 2 3 4'.

join_by_ref array '%s'
echo "$__" # Prints '1%s2%s3%s4'.

join_by_ref 'invalid*' '%s' # Bash 4.4 shows "invalid*[@]: bad substitution".
echo "$__" # Prints nothing but newline.

รู้สึกอิสระที่จะใช้ชื่อที่สะดวกสบายมากขึ้นสำหรับฟังก์ชั่น

ใช้งานได้ตั้งแต่ 3.1 ถึง 5.0-alpha ตามที่สังเกต, การแปรผันของตัวแปรไม่เพียงทำงานกับตัวแปรเท่านั้น แต่ใช้กับตัวแปรอื่นด้วยเช่นกัน

พารามิเตอร์คือเอนทิตีที่เก็บค่า มันอาจเป็นชื่อตัวเลขหรือหนึ่งในตัวละครพิเศษที่แสดงด้านล่างภายใต้พารามิเตอร์พิเศษ ตัวแปรคือพารามิเตอร์ที่แสดงด้วยชื่อ

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

รูปแบบการขยายหรือเปลี่ยนแปลงที่เลือก (เช่นการขยายสตริงย่อย) ที่เบี่ยงเบนการอ้างอิงจากพารามิเตอร์ตัวเองไม่ทำงานอีกต่อไป

ปรับปรุง

ใน Bash 5.0 รุ่นวางจำหน่ายการเปลี่ยนทิศทางตัวแปรเรียกว่าการขยายตัวทางอ้อมแล้วและพฤติกรรมของมันได้รับการบันทึกไว้ในคู่มืออย่างชัดเจนแล้ว:

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

การจดบันทึกว่าในเอกสารของ${parameter}, parameterจะเรียกว่า"พารามิเตอร์เปลือกตามที่อธิบายไว้ (ใน) พารามิเตอร์หรือการอ้างอิงอาร์เรย์ " และในเอกสารประกอบของอาร์เรย์มีการกล่าวถึงว่า"องค์ประกอบใด ๆ ของอาร์เรย์อาจถูกอ้างอิงโดยใช้${name[subscript]}" สิ่งนี้ทำให้__r[@]การอ้างอิงอาร์เรย์

เข้าร่วมโดยรุ่นข้อโต้แย้ง

ฉันในการแสดงความคิดเห็นในคำตอบของคาร์โด้ Galli


2
มีเหตุผลเฉพาะที่จะใช้__เป็นชื่อตัวแปรหรือไม่? ทำให้โค้ดอ่านไม่ได้จริงๆ
PesaThe

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

0

วิธีการนี้จะดูแลช่องว่างภายในค่า แต่ต้องการการวนซ้ำ:

#!/bin/bash

FOO=( a b c )
BAR=""

for index in ${!FOO[*]}
do
    BAR="$BAR,${FOO[$index]}"
done
echo ${BAR:1}

0

ถ้าคุณสร้างอาเรย์ในวงนี่เป็นวิธีง่าย ๆ :

arr=()
for x in $(some_cmd); do
   arr+=($x,)
done
arr[-1]=${arr[-1]%,}
echo ${arr[*]}

0

x=${"${arr[*]}"// /,}

นี่เป็นวิธีที่สั้นที่สุดที่จะทำ

ตัวอย่าง,

arr=(1 2 3 4 5)
x=${"${arr[*]}"// /,}
echo $x  # output: 1,2,3,4,5

1
สิ่งนี้ทำงานไม่ถูกต้องสำหรับสตริงที่มีช่องว่าง: `t = (a" b c "d); echo $ {t [2]} (พิมพ์ "b c"); echo $ {"$ {t [*]}" // /,} (พิมพ์ a, b, c, d)
kounoupis

7
bash: ${"${arr[*]}"// /,}: bad substitution
คาเมรอนฮัดสัน

0

อาจจะสายสำหรับปาร์ตี้ แต่สิ่งนี้ใช้ได้สำหรับฉัน:

function joinArray() {
  local delimiter="${1}"
  local output="${2}"
  for param in ${@:3}; do
    output="${output}${delimiter}${param}"
  done

  echo "${output}"
}

-1

บางทีฉันอาจขาดอะไรบางอย่างที่เห็นได้ชัดเนื่องจากฉันเป็นมือใหม่ถึงสิ่งทุบตี / zsh ทั้งหมด แต่ดูเหมือนว่าฉันจะไม่ต้องการใช้printfเลย และไม่น่าเกลียดที่จะทำโดยไม่ทำเช่นนั้น

join() {
  separator=$1
  arr=$*
  arr=${arr:2} # throw away separator and following space
  arr=${arr// /$separator}
}

อย่างน้อยมันก็ใช้ได้ผลสำหรับฉันจนถึงตอนนี้โดยไม่มีปัญหา

ตัวอย่างเช่นjoin \| *.shที่ฉันว่าใน~ไดเรกทอรีของฉันผลลัพธ์utilities.sh|play.sh|foobar.shไดเรกทอรีเอาท์พุทดีพอสำหรับฉัน

แก้ไข: นี่คือคำตอบของ Nil Geisweillerแต่โดยทั่วไปเป็นฟังก์ชั่น


1
ฉันไม่ใช่ผู้ลงคะแนน แต่การจัดการกับโลกในฟังก์ชั่นนั้นดูแปลกประหลาด
tripleee

-2
liststr=""
for item in list
do
    liststr=$item,$liststr
done
LEN=`expr length $liststr`
LEN=`expr $LEN - 1`
liststr=${liststr:0:$LEN}

นี้จะดูแลเครื่องหมายจุลภาคพิเศษในตอนท้ายด้วย ฉันไม่มีผู้เชี่ยวชาญทุบตี แค่ 2c ของฉันเนื่องจากนี่เป็นระดับประถมศึกษาและเข้าใจได้ง่ายขึ้น


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