Làm thế nào tôi có thể tham gia các phần tử của một mảng trong Bash?


416

Nếu tôi có một mảng như thế này trong Bash:

FOO=( a b c )

Làm thế nào để tôi tham gia các yếu tố với dấu phẩy? Ví dụ, sản xuất a,b,c.

Câu trả lời:


571

Viết lại giải pháp của Pascal Pilz như một hàm trong Bash thuần 100% (không có lệnh bên ngoài):

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

Ví dụ,

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

Ngoài ra, chúng tôi có thể sử dụng printf để hỗ trợ các ký tự phân cách nhiều ký tự, sử dụng ý tưởng của @gniourf_gniourf

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

Ví 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
Sử dụng công cụ này cho các phân tách đa vi khuẩn: hàm tham gia {perl -e '$ s = shift @ARGV; in tham gia ($ s, @ARGV); ' "$ @"; } tham gia ',' abc # a, b, c
Daniel Patru

4
@dpatru nào để làm bash thuần túy đó?
CMCDragonkai

4
@puchu Cái gì không hoạt động là dấu phân cách nhiều ký tự. Nói "không gian không hoạt động" làm cho nó có vẻ như tham gia với một không gian không hoạt động. Nó làm.
Eric

6
Điều này thúc đẩy các subshell sinh sản nếu lưu trữ đầu ra thành biến. Sử dụng konsoleboxphong cách :) function join { local IFS=$1; __="${*:2}"; }hoặc function join { IFS=$1 eval '__="${*:2}"'; }. Sau đó sử dụng __sau. Có, tôi là người thúc đẩy sử dụng __như một biến kết quả;) (và một biến lặp chung hoặc biến tạm thời). Nếu khái niệm này được chuyển đến một trang wiki Bash nổi tiếng, họ đã sao chép tôi :)
konsolebox

6
Đừng đặt phần mở rộng $dtrong định dạng định dạng của printf. Bạn nghĩ rằng bạn đã an toàn vì bạn đã thoát khỏi mạng %nhưng có một số cảnh báo khác: khi dấu phân cách chứa dấu gạch chéo ngược (ví dụ \n:) hoặc khi dấu phân cách bắt đầu bằng dấu gạch nối (và có thể bây giờ tôi không thể nghĩ đến dấu gạch ngang). Tất nhiên bạn có thể khắc phục những điều này (thay thế dấu gạch chéo ngược bằng dấu gạch chéo kép và sử dụng printf -- "$d%s"), nhưng đến một lúc nào đó bạn sẽ cảm thấy rằng bạn đang chiến đấu chống lại vỏ thay vì làm việc với nó. Đó là lý do tại sao, trong câu trả lời của tôi dưới đây, tôi đã đưa ra dấu phân cách cho các điều khoản được tham gia.
gniourf_gniourf

206

Một giải pháp khác:

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

echo $bar

Chỉnh sửa: giống nhau nhưng cho dấu phân cách độ dài biến đa ký tự:

#!/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. Những gì về printf -v bar ",%s" "${foo[@]}". Nó là một forkít (thực sự clone). Nó thậm chí còn không đọc được một tập tin : printf -v bar ",%s" $(<infile).
TrueY

14
Thay vì cầu nguyện $separatorkhông chứa %shoặc như vậy, bạn có thể làm cho printfmạnh mẽ của bạn : printf "%s%s" "$separator" "${foo[@]}".
musiphil

5
@musiphil Sai. Từ bash man: "Định dạng được sử dụng lại khi cần thiết để tiêu thụ tất cả các đối số. Sử dụng hai trình giữ chỗ định dạng như trong printf "%s%s"sẽ sử dụng dấu phân cách trong tập hợp đầu tiên CHỈ đầu ra, và sau đó chỉ cần nối các phần còn lại của đối số.
AnyDev

3
@AndrDevEK: Cảm ơn bạn đã bắt lỗi. Thay vào đó tôi sẽ đề nghị một cái gì đó như printf "%s" "${foo[@]/#/$separator}".
musiphil

2
@musiphil, Cảm ơn. Đúng! Sau đó printf trở nên dư thừa và dòng đó có thể được giảm xuống IFS=; regex="${foo[*]/#/$separator}". Tại thời điểm này, điều này về cơ bản trở thành câu trả lời của gniourf_gniourf mà IMO sạch hơn ngay từ đầu, nghĩa là sử dụng chức năng để giới hạn phạm vi thay đổi IFS và bình tạm thời.
AnyDev

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

3
Các dấu ngoặc kép bên ngoài và dấu ngoặc kép xung quanh dấu hai chấm là không cần thiết. Chỉ có dấu ngoặc kép bên trong là cần thiết:bar=$( IFS=, ; echo "${foo[*]}" )
ceving

8
+1 cho giải pháp nhỏ gọn nhất không cần vòng lặp, không cần các lệnh bên ngoài và không áp đặt các hạn chế bổ sung đối với bộ ký tự của các đối số.
ceving

22
Tôi thích giải pháp, nhưng nó chỉ hoạt động nếu IFS là một nhân vật
Jayen

8
Bất cứ ý tưởng tại sao điều này không hoạt động nếu sử dụng @thay vì *, như trong $(IFS=, ; echo "${foo[@]}")? Tôi có thể thấy rằng *đã bảo toàn khoảng trắng trong các phần tử, một lần nữa không chắc chắn làm thế nào, vì @thường được yêu cầu cho mục đích này.
haridsv

10
Tôi tìm thấy câu trả lời cho câu hỏi của riêng tôi ở trên. Câu trả lời là IFS chỉ được công nhận *. Trong trang bash man, tìm kiếm "Thông số đặc biệt" và tìm lời giải thích bên cạnh *:
haridsv

66

Có lẽ, ví dụ,

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

echo "$FOOJOIN"

3
Nếu bạn làm điều đó, nó nghĩ rằng IFS- là biến. Bạn phải làm echo "-${IFS}-"(dấu ngoặc nhọn tách dấu gạch ngang khỏi tên biến).
Tạm dừng cho đến khi có thông báo mới.

1
Vẫn nhận được kết quả tương tự (Tôi chỉ cần đặt dấu gạch ngang để minh họa điểm echo $IFSmà anh ấy làm điều tương tự.
David Wolever

41
Điều đó nói rằng, điều này dường như vẫn hoạt động. Vì vậy, giống như hầu hết mọi thứ với Bash, tôi sẽ giả vờ như tôi hiểu nó và tiếp tục cuộc sống của mình.
David Wolever

2
"-" không phải là ký tự hợp lệ cho tên biến, do đó, shell thực hiện đúng khi bạn sử dụng $ IFS-, bạn không cần $ {IFS} - (bash, ksh, sh và zsh trong linux và solaris cũng đồng ý).
Idelic

2
@David sự khác biệt giữa tiếng vang của bạn và Dennis là anh ấy đã sử dụng trích dẫn kép. Nội dung của IFS được sử dụng 'trên đầu vào' như một tuyên bố về các ký tự phân tách từ - vì vậy bạn sẽ luôn nhận được một dòng trống không có dấu ngoặc kép.
martin clayton

30

Đáng ngạc nhiên là giải pháp của tôi chưa được đưa ra :) Đây là cách đơn giản nhất đối với tôi. Nó không cần một chức năng:

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

Lưu ý: Giải pháp này được quan sát là hoạt động tốt ở chế độ không POSIX. Trong chế độ POSIX , các yếu tố vẫn được nối đúng, nhưng IFS=,trở thành vĩnh viễn.


không may chỉ hoạt động cho các ký tự phân cách một ký tự
maoizm

24

Đây là hàm Bash thuần 100% thực hiện công việc:

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}"
}

Nhìn:

$ 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


$

Điều này bảo tồn ngay cả các dòng mới, và không cần một lớp con để có được kết quả của hàm. Nếu bạn không thích printf -v(tại sao bạn không thích nó?) Và truyền tên biến, tất nhiên bạn có thể sử dụng biến toàn cục cho chuỗi trả 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
Giải pháp cuối cùng của bạn rất tốt, nhưng có thể được làm sạch hơn bằng cách tạo join_retmột biến cục bộ, và sau đó lặp lại nó ở cuối. Điều này cho phép phép nối () được sử dụng theo cách viết kịch bản shell thông thường, ví dụ $(join ":" one two three), và không yêu cầu biến toàn cục.
James Sneeringer

1
@JamesSneeringer Tôi cố tình sử dụng thiết kế này để tránh các subshells. Trong kịch bản shell, không giống như nhiều ngôn ngữ khác, các biến toàn cục được sử dụng theo cách đó không nhất thiết là một điều xấu; đặc biệt là nếu họ ở đây để giúp tránh các subshells. Hơn nữa, $(...)trims traillines mới; vì vậy, nếu trường cuối cùng của mảng chứa các dòng mới, chúng sẽ được cắt bớt (xem bản demo trong đó chúng không được cắt theo thiết kế của tôi).
gniourf_gniourf

Điều này hoạt động với các dấu phân cách đa ký tự, khiến tôi hạnh phúc ^ _ ^
spiffytech

Để giải quyết "tại sao bạn không thích printf -v?": Trong Bash, các biến cục bộ không thực sự có chức năng cục bộ, vì vậy bạn có thể làm những việc như thế này. (Gọi hàm F1 với biến cục bộ x, lần lượt gọi hàm f2 sửa đổi x - được khai báo cục bộ trong phạm vi của F1) Nhưng đó không thực sự là cách các biến cục bộ phải hoạt động. Nếu các biến cục bộ thực sự là cục bộ (hoặc được giả sử là, ví dụ, trong một tập lệnh phải hoạt động trên cả bash và ksh) thì nó sẽ gây ra vấn đề với toàn bộ "trả về một giá trị bằng cách lưu trữ nó trong biến với tên này".
tetsujin

15

Điều này không quá khác biệt so với các giải pháp hiện có, nhưng nó tránh sử dụng một chức năng riêng biệt, không sửa đổi IFStrong vỏ cha mẹ và tất cả chỉ trong một dòng:

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

dẫn đến

a,b,c

Giới hạn: dấu phân cách không thể dài hơn một ký tự.


13

Không sử dụng lệnh bên ngoài:

$ 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

Cảnh báo, nó giả sử các phần tử không có khoảng trắng.


4
Nếu bạn không muốn sử dụng một biến trung gian, nó có thể được thực hiện thậm chí ngắn hơn:echo ${FOO[@]} | tr ' ' ','
jesjimher

2
Tôi không hiểu những phiếu bầu tiêu cực. Đó là một giải pháp nhỏ gọn và dễ đọc hơn các giải pháp khác được đăng ở đây và rõ ràng cảnh báo rằng nó không hoạt động khi có không gian.
jesjimher

12

Tôi sẽ lặp lại mảng dưới dạng một chuỗi, sau đó chuyển đổi khoảng trắng thành nguồn cấp dữ liệu và sau đó sử dụng pasteđể nối mọi thứ trong một dòng như vậy:

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

Các kết quả:

a,b,c

Đây dường như là cách nhanh nhất và sạch nhất đối với tôi!


$FOOchỉ là phần tử đầu tiên của mảng. Ngoài ra, điều này phá vỡ cho các phần tử mảng có chứa khoảng trắng.
Benjamin W.

9

Với việc sử dụng lại giải pháp @ không quan trọng, nhưng với một tuyên bố bằng cách tránh trạm biến áp $ {: 1} và cần một biến trung gian.

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

printf có 'Chuỗi định dạng được sử dụng lại thường xuyên khi cần thiết để đáp ứng các đối số.' trong các trang man của nó, sao cho các chuỗi của chuỗi được ghi lại. Sau đó, mẹo là sử dụng độ dài LIST để cắt người điều phối cuối cùng, vì việc cắt sẽ chỉ giữ lại chiều dài của LIST khi các trường được tính.


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

8
Bạn nên xác thịt trả lời.
joce

Một trong những tốt nhất. Cảm ơn!!
peter pan gz

4
Tôi ước tôi có thể hạ thấp câu trả lời này vì nó mở ra một lỗ hổng bảo mật và bởi vì nó sẽ phá hủy không gian trong các yếu tố.
lươn ghEEz

1
@bxm thực sự, nó dường như bảo tồn các không gian và nó không cho phép thoát khỏi bối cảnh đối số tiếng vang. Tôi hình dung rằng việc thêm @Qcó thể thoát khỏi các giá trị đã tham gia khỏi việc hiểu sai khi họ có một người tham gia trong đó: foo=("a ," "b ' ' c" "' 'd e" "f " ";" "ls -latr"); s=$(IFS=, eval 'echo "${foo[*]@Q}"'); echo "${s}"đầu ra'a ,','b '\'' '\'' c',''\'' '\''d e','f ',';','ls -latr '
lươn ghEEz

1
Tránh các giải pháp sử dụng các subshells trừ khi cần thiết.
konsolebox

5

giải pháp printf chấp nhận dấu phân cách có độ dài bất kỳ (dựa trên câu trả lời @ không quan trọng)

#/!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

Điều này tạo ra đầu ra với dấu phẩy hàng đầu.
Đánh dấu Renouf

Thanh cuối cùng = $ {bar: $ {# sep}} xóa dấu phân cách. Tôi chỉ cần sao chép và dán trong bash shell và nó hoạt động. Bạn đang sử dụng vỏ gì?
Riccardo Galli

2
Bất kỳ printf định dạng định dạng nào (ví dụ: %svô tình vào $sepsẽ gây ra sự cố.
Peter.O

sepcó thể được vệ sinh với ${sep//\%/%%}. Tôi thích giải pháp của bạn tốt hơn ${bar#${sep}}hoặc ${bar%${sep}}(thay thế). Điều này là tốt nếu được chuyển đổi thành một hàm lưu trữ kết quả thành một biến chung như thế __, chứ không phải echonó.
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

Điều này nên ở trên cùng.
Eric Walker

6
Điều này không nên ở trên cùng: nếu như HISTSIZE=0?
har-wradim 7/11/2015

@ har-wradim, mẹo paste -sd,không phải là về việc sử dụng lịch sử.
Veda

@Veda Không, đó là về việc sử dụng kết hợp, và nó sẽ không hoạt động nếu HISTSIZE=0- hãy thử.
har-wradim

4

Phiên bản ngắn hơn của câu trả lời hàng đầu:

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

Sử dụng:

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

1
Một phiên bản dài hơn, nhưng không cần tạo một bản sao của một lát các đối số thành một biến mảng:join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '%s' "${@/#/$d}"; }
Rockallite

Phiên bản khác: Phiên bản join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '$d%s' "${@}"; } này hoạt động với cách sử dụng: join_strings 'delim' "${array[@]}"hoặc không được trích dẫn:join_strings 'delim' ${array[@]}
Cometsong

4

Kết hợp tốt nhất của tất cả các thế giới cho đến nay với ý tưởng sau đây.

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

Kiệt tác nhỏ này là

  • Bash thuần 100% (mở rộng tham số với IFS tạm thời không được đặt, không có cuộc gọi bên ngoài, không printf ...)
  • nhỏ gọn, hoàn chỉnh và hoàn hảo (hoạt động với các bộ giới hạn đơn và đa ký tự, hoạt động với các bộ giới hạn chứa khoảng trắng, ngắt dòng và các ký tự đặc biệt khác của vỏ, hoạt động với dấu phân cách trống)
  • hiệu quả (không có subshell, không có bản sao mảng)
  • đơn giản và ngu ngốc và, ở một mức độ nhất định, đẹp và hướng dẫn là tốt

Ví dụ:

$ 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
Không hay lắm: ít nhất 2 vấn đề: 1. join_ws ,(không có đối số) đầu ra sai ,,. 2. join_ws , -ekhông xuất ra kết quả gì (đó là do bạn sử dụng sai echothay vì printf). Tôi thực sự không biết lý do tại sao bạn quảng cáo việc sử dụng echothay vì printf: echobị phá vỡ khét tiếng và printflà một nội dung mạnh mẽ.
gniourf_gniourf

1

Ngay bây giờ tôi đang sử dụng:

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 ' ' ','`"

Cái nào hoạt động, nhưng (trong trường hợp chung) sẽ phá vỡ khủng khiếp nếu các phần tử mảng có một khoảng trắng trong chúng.

(Đối với những người quan tâm, đây là tập lệnh bao quanh pep8.py )


bạn lấy những giá trị mảng đó từ đâu? nếu bạn đang mã hóa nó như thế, tại sao không chỉ foo = "a, b, c".?
ghostdog74

Trong trường hợp này tôi thực sự đang cứng mã hóa các giá trị, nhưng tôi muốn đặt chúng trong một mảng để tôi có thể nhận xét về từng cá nhân. Tôi đã cập nhật câu trả lời để cho bạn biết ý tôi là gì.
David Wolever

Giả sử bạn đang thực sự sử dụng bash, điều này có thể hoạt động tốt hơn : ARGS="--ignore $(echo "${TO_IGNORE[@]}" | tr ' ' ',')". Toán tử $()mạnh hơn phân tích ngược (cho phép lồng $()""). Gói ${TO_IGNORE[@]}với dấu ngoặc kép cũng sẽ giúp.
kevinarpe

1

Nỗ lực của tôi.

$ array=(one two "three four" five)
$ echo "${array[0]}$(printf " SEP %s" "${array[@]:1}")"
one SEP two SEP three four SEP five

1

Sử dụng perl cho các phân tách đa vi khuẩn:

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

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

Hoặc trong một dòng:

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

làm việc cho tôi, mặc dù jointên xung đột với một số tào lao trên OS X.. tôi sẽ gọi nó conjoined, hoặc có thể jackie_joyner_kersee?
Alex Gray

1

Cảm ơn @gniourf_gniourf đã nhận xét chi tiết về sự kết hợp thế giới tốt nhất của tôi cho đến nay. Xin lỗi vì đăng mã không được thiết kế và kiểm tra kỹ lưỡng. Đây là một thử tốt hơn.

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

Vẻ đẹp này theo quan niệm là

  • (vẫn) bash nguyên chất 100% (cảm ơn vì đã chỉ ra rõ ràng rằng printf cũng là một nội dung. Tôi không biết về điều này trước đây ...)
  • làm việc với các dấu phân cách đa ký tự
  • gọn hơn và đầy đủ hơn và lần này đã suy nghĩ cẩn thận và kiểm tra căng thẳng dài hạn với các chuỗi ngẫu nhiên từ các tập lệnh shell trong số các tập lệnh khác, bao gồm việc sử dụng các ký tự đặc biệt hoặc ký tự điều khiển hoặc không có ký tự trong cả dấu tách và / hoặc tham số, và các trường hợp cạnh và các trường hợp góc và các vấn đề khác như không có đối số nào cả. Điều đó không đảm bảo không có thêm lỗi, nhưng sẽ khó hơn một chút để tìm ra nó. BTW, ngay cả những câu trả lời được bình chọn hàng đầu hiện nay và có liên quan phải chịu đựng những điều như vậy - đó là lỗi ...

Ví dụ khác:

$ 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

Trong trường hợp các phần tử bạn muốn tham gia không phải là một mảng chỉ là một chuỗi được phân tách bằng dấu cách, bạn có thể làm một cái gì đó như thế này:

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

ví dụ, trường hợp sử dụng của tôi là một số chuỗi được truyền trong tập lệnh shell của tôi và tôi cần sử dụng chuỗi này để chạy trên truy vấn SQL:

./my_script "aa bb cc dd"

Trong my_script, tôi cần thực hiện "CHỌN * TỪ bảng WHERE tên IN ('aa', 'bb', 'cc', 'dd'). Sau đó, lệnh trên sẽ hữu ích.


Bạn có thể sử dụng printf -v bar ...thay vì phải chạy vòng lặp printf trong một khung con và chụp đầu ra.
codeforester

tất cả các giải pháp nâng cấp ưa thích ở trên không hoạt động nhưng giải pháp thô sơ của bạn đã làm cho tôi (Y)
ishandutta2007

1

Đây là một trong đó hầu hết các shell tương thích POSIX hỗ trợ:

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[@]}"
}

Đó là mã Bash tốt, nhưng POSIX hoàn toàn không có mảng (hoặc local).
Anders Kaseorg

@Anders: Vâng tôi đã học được điều này rất khó gần đây :( Tôi sẽ bỏ nó ngay bây giờ vì hầu hết các shell tương thích POSIX dường như hỗ trợ mảng.
user541686

1

Sử dụng biến gián tiếp để tham chiếu trực tiếp đến một mảng cũng hoạt động. Các tài liệu tham khảo được đặt tên cũng có thể được sử dụng, nhưng chúng chỉ có sẵn trong 4.3.

Ưu điểm của việc sử dụng dạng hàm này là bạn có thể có dấu phân cách tùy chọn (mặc định là ký tự đầu tiên của mặc định IFS, là khoảng trắng; có thể biến nó thành một chuỗi trống nếu bạn muốn) và nó tránh mở rộng giá trị hai lần (lần đầu tiên khi được truyền dưới dạng tham số và thứ hai là "$@"bên trong hàm).

Giải pháp này cũng không yêu cầu người dùng gọi hàm bên trong thay thế lệnh - triệu tập một chuỗi con, để có được một phiên bản nối của chuỗi được gán cho một biến khác.

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.

Hãy sử dụng một tên thoải mái hơn cho chức năng.

Điều này hoạt động từ 3,1 đến 5,0-alpha. Theo quan sát, chỉ định biến không chỉ hoạt động với các biến mà còn với các tham số khác.

Một tham số là một thực thể lưu trữ các giá trị. Nó có thể là tên, số hoặc một trong các ký tự đặc biệt được liệt kê bên dưới trong Thông số đặc biệt. Một biến là một tham số được biểu thị bằng một tên.

Mảng và các phần tử mảng cũng là các tham số (các thực thể lưu trữ giá trị) và các tham chiếu đến các mảng cũng là các tham chiếu kỹ thuật cho các tham số. Và giống như tham số đặc biệt @, array[@]cũng làm cho một tham chiếu hợp lệ.

Các hình thức mở rộng đã thay đổi hoặc chọn lọc (như mở rộng chuỗi con) làm sai lệch tham chiếu khỏi chính tham số không còn hoạt động.

Cập nhật

Trong phiên bản phát hành của Bash 5.0, cảm ứng biến được gọi là mở rộng gián tiếp và hành vi của nó đã được ghi lại rõ ràng trong hướng dẫn:

Nếu ký tự đầu tiên của tham số là dấu chấm than (!) Và tham số không phải là nameref, thì nó đưa ra một mức độ gián tiếp. Bash sử dụng giá trị được hình thành bằng cách mở rộng phần còn lại của tham số làm tham số mới; điều này sau đó được mở rộng và giá trị đó được sử dụng trong phần còn lại của việc mở rộng, thay vì mở rộng tham số ban đầu. Điều này được gọi là mở rộng gián tiếp.

Lưu ý rằng trong tài liệu của ${parameter}, parameterđược gọi là "tham số shell như được mô tả (trong) PARAMETERS hoặc tham chiếu mảng ". Và trong tài liệu về mảng, có đề cập rằng "Bất kỳ phần tử nào của mảng có thể được tham chiếu bằng cách sử dụng ${name[subscript]}". Điều này làm cho __r[@]một tài liệu tham khảo mảng.

Tham gia bằng phiên bản đối số

Xem bình luận của tôi trong câu trả lời của Riccardo Galli .


2
Có một lý do cụ thể để sử dụng __như một tên biến? Làm cho mã thực sự không thể đọc được.
PesaThe 15/07/18

@PesaThe chỉ là một sở thích. Tôi thích sử dụng tên chung cho một biến trả về. Các tên không chung khác tự gán cho các chức năng cụ thể và nó yêu cầu ghi nhớ. Gọi nhiều hàm trả về giá trị trên các biến khác nhau có thể làm cho mã ít dễ theo dõi hơn. Sử dụng một tên chung sẽ buộc bộ biên dịch chuyển giá trị từ biến trả về sang biến thích hợp để tránh xung đột và nó làm cho mã cuối cùng trở nên dễ đọc hơn vì nó trở nên rõ ràng khi các giá trị được trả về. Tôi làm cho một vài ngoại lệ cho quy tắc đó mặc dù.
konsolebox

0

Cách tiếp cận này chăm sóc các khoảng trắng trong các giá trị, nhưng yêu cầu một vòng lặp:

#!/bin/bash

FOO=( a b c )
BAR=""

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

0

Nếu bạn xây dựng mảng trong một vòng lặp, đây là một cách đơn giản:

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

0

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

Đây là cách ngắn nhất để làm điều đó.

Thí dụ,

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

1
Điều này không hoạt động chính xác cho chuỗi có khoảng trắng: `t = (a" b c "d); echo $ {t [2]} (in "b c"); echo $ {"$ {t [*]}" // /,} (in a, b, c, d)
kounoupis

7
bash: ${"${arr[*]}"// /,}: bad substitution
Cameron Hudson

0

Có lẽ muộn cho bữa tiệc, nhưng điều này làm việc cho tôi:

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

  echo "${output}"
}

-1

Có lẽ tôi đang thiếu một cái gì đó rõ ràng, vì tôi là người mới trong toàn bộ điều bash / zsh, nhưng có vẻ như tôi không cần sử dụng printfgì cả. Nó cũng không thực sự xấu xí để làm mà không có.

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

Ít nhất, nó đã làm việc cho tôi cho đến nay mà không có vấn đề.

Chẳng hạn, join \| *.shtrong đó, giả sử tôi đang ở trong ~thư mục của mình , xuất ra utilities.sh|play.sh|foobar.sh. Đủ tôt cho tôi.

EDIT: Về cơ bản đây là câu trả lời của Nil Geisweiller , nhưng được khái quát thành một chức năng.


1
Tôi không phải là người hạ cấp, nhưng thao túng toàn cầu trong một chức năng có vẻ khá kỳ quặc.
tripleee

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

Điều này cũng quan tâm đến dấu phẩy thừa ở cuối. Tôi không phải là chuyên gia bash. Chỉ là 2c của tôi, vì điều này là cơ bản và dễ hiểu hơn


-2
awk -v sep=. 'BEGIN{ORS=OFS="";for(i=1;i<ARGC;i++){print ARGV[i],ARGC-i-1?sep:""}}' "${arr[@]}"

hoặc là

$ a=(1 "a b" 3)
$ b=$(IFS=, ; echo "${a[*]}")
$ echo $b
1,a b,3
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.