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
.
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:
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
konsolebox
phong 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 :)
$d
trong đị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.
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
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)
.
$separator
không chứa %s
hoặc như vậy, bạn có thể làm cho printf
mạnh mẽ của bạn : printf "%s%s" "$separator" "${foo[@]}"
.
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ố.
printf "%s" "${foo[@]/#/$separator}"
.
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.
$ foo=(a "b c" d)
$ bar=$(IFS=, ; echo "${foo[*]}")
$ echo "$bar"
a,b c,d
bar=$( IFS=, ; echo "${foo[*]}" )
@
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.
*
. 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 *
:
Có lẽ, ví dụ,
SAVE_IFS="$IFS"
IFS=","
FOOJOIN="${FOO[*]}"
IFS="$SAVE_IFS"
echo "$FOOJOIN"
echo "-${IFS}-"
(dấu ngoặc nhọn tách dấu gạch ngang khỏi tên biến).
echo $IFS
mà anh ấy làm điều tương tự.
Đá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.
Đâ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}"
}
join_ret
mộ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.
$(...)
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).
Đ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 IFS
trong 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ự.
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.
echo ${FOO[@]} | tr ' ' ','
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!
$FOO
chỉ 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.
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.
s=$(IFS=, eval 'echo "${FOO[*]}"')
@Q
có 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 '
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
printf
định dạng định dạng nào (ví dụ: %s
vô tình vào $sep
sẽ gây ra sự cố.
sep
có 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 echo
nó.
function join_by { printf -v __ "${1//\%/%%}%s" "${@:2}"; __=${__:${#1}}; }
$ set a 'b c' d
$ history -p "$@" | paste -sd,
a,b c,d
HISTSIZE=0
?
paste -sd,
không phải là về việc sử dụng lịch sử.
HISTSIZE=0
- hãy thử.
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[@]}"
join_strings () { local d="$1"; echo -n "$2"; shift 2 && printf '%s' "${@/#/$d}"; }
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[@]}
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à
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
join_ws ,
(không có đối số) đầu ra sai ,,
. 2. join_ws , -e
không xuất ra kết quả gì (đó là do bạn sử dụng sai echo
thay 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 echo
thay vì printf
: echo
bị phá vỡ khét tiếng và printf
là một nội dung mạnh mẽ.
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 )
ARGS="--ignore $(echo "${TO_IGNORE[@]}" | tr ' ' ',')"
. Toán tử $()
mạnh hơn phân tích ngược (cho phép lồng $()
và ""
). Gói ${TO_IGNORE[@]}
với dấu ngoặc kép cũng sẽ giúp.
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
join
tê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
?
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í 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 $
$
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.
printf -v bar ...
thay vì phải chạy vòng lặp printf trong một khung con và chụp đầu ra.
Đâ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[@]}"
}
local
).
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.
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.
Xem bình luận của tôi trong câu trả lời của Riccardo Galli .
__
như một tên biến? Làm cho mã thực sự không thể đọc được.
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}
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[*]}
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
bash: ${"${arr[*]}"// /,}: bad substitution
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 printf
gì 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 \| *.sh
trong đó, 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.
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