Vòng qua một chuỗi các chuỗi trong Bash?


1501

Tôi muốn viết một tập lệnh lặp qua 15 chuỗi (có thể là mảng?) Có được không?

Cái gì đó như:

for databaseName in listOfNames
then
  # Do something
end

Câu trả lời:


2391

Bạn có thể sử dụng nó như thế này:

## declare an array variable
declare -a arr=("element1" "element2" "element3")

## now loop through the above array
for i in "${arr[@]}"
do
   echo "$i"
   # or do whatever with individual element of the array
done

# You can access them using echo "${arr[0]}", "${arr[1]}" also

Cũng hoạt động cho khai báo mảng nhiều dòng

declare -a arr=("element1" 
                "element2" "element3"
                "element4"
                )

777

Điều đó là có thể, tất nhiên.

for databaseName in a b c d e f; do
  # do something like: echo $databaseName
done 

Xem Bash Loops, trong khi và cho đến khi biết chi tiết.


18
Vấn đề với phương pháp này là gì? Trong những trường hợp đơn giản, nó dường như hoạt động và, sau đó, trực quan hơn câu trả lời của @ anubhava.
Tiến sĩ Jan-Philip Gehrcke

17
Điều này đặc biệt tốt với sự thay thế lệnh, ví dụ for year in $(seq 2000 2013).
Brad Koch

23
Vấn đề là anh ta hỏi về việc lặp qua một mảng.
mgasms

20
Phương pháp 'khai báo' hoạt động tốt nhất nếu bạn phải lặp lại trên cùng một mảng ở nhiều nơi. Cách tiếp cận này sạch hơn nhưng kém linh hoạt.
StampyCode

15
Tại sao không phải là số 1 này? Nó sạch hơn và bạn có thể dễ dàng sử dụng lại mảng chỉ bằng cách đặt một chuỗi, tức là , DATABASES="a b c d e f".
Nerdmaster

201

Không có câu trả lời nào trong số đó bao gồm một bộ đếm ...

#!/bin/bash
## declare an array variable
declare -a array=("one" "two" "three")

# get length of an array
arraylength=${#array[@]}

# use for loop to read all values and indexes
for (( i=1; i<${arraylength}+1; i++ ));
do
  echo $i " / " ${arraylength} " : " ${array[$i-1]}
done

Đầu ra:

1  /  3  :  one
2  /  3  :  two
3  /  3  :  three

7
Đây phải là câu trả lời được chấp nhận, là câu trả lời duy nhất hoạt động khi các phần tử mảng chứa khoảng trắng.
jesjimher

1
Đó chỉ là vì lợi ích của đầu ra của ví dụ với một bộ đếm. Nó cũng khá tầm thường để thay đổi điều đó, và hoạt động tương tự.
caktux

7
Tiếng vang ở cuối là lỗi. Bạn không cần trích dẫn các hằng số, bạn cần trích dẫn các mở rộng hoặc có thể trích dẫn một cách an toàn như sau: echo "$i / ${arraylength} : ${array[$i-1]}"- nếu không, nếu bạn $ichứa một quả địa cầu thì nó sẽ được mở rộng, nếu nó chứa một tab thì nó sẽ được thay đổi thành một không gian, v.v.
Charles Duffy

10
@bzeaman, chắc chắn - nhưng nếu bạn cẩu thả về những điều đó thì nó yêu cầu phân tích theo ngữ cảnh (như bạn vừa làm) để chứng minh điều gì đó chính xác và phân tích lại nếu bối cảnh đó thay đổi hoặc mã được sử dụng lại ở một nơi khác hoặc cho bất cứ lý do nào khác một luồng kiểm soát bất ngờ là có thể. Viết nó theo cách mạnh mẽ và nó chính xác bất kể bối cảnh.
Charles Duffy

5
Điều này sẽ không làm việc cho một mảng thưa thớt, tức là nếu mảng bị thiếu các phần tử. Ví dụ: nếu bạn có các phần tử mảng A [1] = 'xx', A [4] = 'yy' và A [9] = 'zz', độ dài sẽ là 3 và vòng lặp sẽ không xử lý tất cả các phần tử .
Ông Ed

145

Đúng

for Item in Item1 Item2 Item3 Item4 ;
  do
    echo $Item
  done

Đầu ra:

Item1
Item2
Item3
Item4

Để bảo tồn không gian; danh sách trích dẫn đơn hoặc đôi và mở rộng danh sách báo giá kép.

for Item in 'Item 1' 'Item 2' 'Item 3' 'Item 4' ;
  do
    echo "$Item"
  done

Đầu ra:

Item 1
Item 2
Item 3
Item 4

Để tạo danh sách qua nhiều dòng

for Item in Item1 \
            Item2 \
            Item3 \
            Item4
  do
    echo $Item
  done

Đầu ra:

Item1
Item2
Item3
Item4


Biến danh sách đơn giản

List=( Item1 Item2 Item3 )

hoặc là

List=(
      Item1 
      Item2 
      Item3
     )

Hiển thị biến danh sách:

echo ${List[*]}

Đầu ra:

Item1 Item2 Item3

Lặp lại danh sách:

for Item in ${List[*]} 
  do
    echo $Item 
  done

Đầu ra:

Item1
Item2
Item3

Tạo một chức năng để đi qua một danh sách:

Loop(){
  for item in ${*} ; 
    do 
      echo ${item} 
    done
}
Loop ${List[*]}

Sử dụng từ khóa khai báo (lệnh) để tạo danh sách, về mặt kỹ thuật được gọi là một mảng:

declare -a List=(
                 "element 1" 
                 "element 2" 
                 "element 3"
                )
for entry in "${List[@]}"
   do
     echo "$entry"
   done

Đầu ra:

element 1
element 2
element 3

Tạo một mảng kết hợp. Một cuốn từ điển:

declare -A continent

continent[Vietnam]=Asia
continent[France]=Europe
continent[Argentina]=America

for item in "${!continent[@]}"; 
  do
    printf "$item is in ${continent[$item]} \n"
  done

Đầu ra:

 Argentina is in America
 Vietnam is in Asia
 France is in Europe

CVS biến hoặc tập tin vào một danh sách .
Thay đổi dấu phân cách trường bên trong từ một khoảng trắng, thành những gì bạn muốn.
Trong ví dụ dưới đây, nó được thay đổi thành dấu phẩy

List="Item 1,Item 2,Item 3"
Backup_of_internal_field_separator=$IFS
IFS=,
for item in $List; 
  do
    echo $item
  done
IFS=$Backup_of_internal_field_separator

Đầu ra:

Item 1
Item 2
Item 3

Nếu cần đánh số chúng:

` 

đây được gọi là đánh dấu lại. Đặt lệnh bên trong tick lại.

`commend` 

Nó nằm cạnh số một trên bàn phím của bạn và hoặc trên phím tab. Trên bàn phím tiếng Anh chuẩn của người Mỹ.

List=()
Start_count=0
Step_count=0.1
Stop_count=1
for Item in `seq $Start_count $Step_count $Stop_count`
    do 
       List+=(Item_$Item)
    done
for Item in ${List[*]}
    do 
        echo $Item
    done

Đầu ra là:

Item_0.0
Item_0.1
Item_0.2
Item_0.3
Item_0.4
Item_0.5
Item_0.6
Item_0.7
Item_0.8
Item_0.9
Item_1.0

Làm quen với hành vi bash:

Tạo một danh sách trong một tập tin

cat <<EOF> List_entries.txt
Item1
Item 2 
'Item 3'
"Item 4"
Item 7 : *
"Item 6 : * "
"Item 6 : *"
Item 8 : $PWD
'Item 8 : $PWD'
"Item 9 : $PWD"
EOF

Đọc tệp danh sách vào một danh sách và hiển thị

List=$(cat List_entries.txt)
echo $List
echo '$List'
echo "$List"
echo ${List[*]}
echo '${List[*]}'
echo "${List[*]}"
echo ${List[@]}
echo '${List[@]}'
echo "${List[@]}"

Hướng dẫn tham khảo dòng lệnh BASH: Ý nghĩa đặc biệt của các ký tự hoặc từ nhất định đối với shell.


7
CÁI NÀY SAI. Cần phải được "${List[@]}"chính xác, với các trích dẫn . ${List[@]}sai. ${List[*]}sai. Hãy thử List=( "* first item *" "* second item *" )- bạn sẽ có hành vi đúng for item in "${List[@]}"; do echo "$item"; done, nhưng không phải từ bất kỳ biến thể nào khác.
Charles Duffy

Có ký tự đặc biệt được giải thích, có thể hoặc không thể mong muốn. Tôi cập nhật câu trả lời để bao gồm.
FireInTheSky

2
Tôi vẫn sẽ đề nghị mạnh mẽ cho thấy cách tiếp cận chính xác / mạnh mẽ hơn trước . Mọi người thường lấy câu trả lời đầu tiên có vẻ như nó sẽ hoạt động; nếu câu trả lời đó có những cảnh báo ẩn, họ chỉ có thể phơi bày sau đó. (Nó không chỉ là ký tự đại diện được phá vỡ bởi thiếu trích dẫn; List=( "first item" "second item" )sẽ bị phá vỡ vào first, item, second, itemcũng).
Charles Duffy

Bạn cũng có thể cân nhắc việc tránh sử dụng một ví dụ có thể khiến mọi người phân tích lsđầu ra, trái với các thực tiễn tốt nhất .
Charles Duffy

1
Bạn đang bác bỏ những tuyên bố mà tôi chưa bao giờ đưa ra. Tôi khá quen thuộc với ngữ nghĩa trích dẫn của bash - xem stackoverflow.com/tags/bash/topusers
Charles Duffy

108

Theo tinh thần như câu trả lời của 4ndrew:

listOfNames="RA
RB
R C
RD"

# To allow for other whitespace in the string:
# 1. add double quotes around the list variable, or
# 2. see the IFS note (under 'Side Notes')

for databaseName in "$listOfNames"   #  <-- Note: Added "" quotes.
do
  echo "$databaseName"  # (i.e. do action / processing of $databaseName here...)
done

# Outputs
# RA
# RB
# R C
# RD

B. Không có khoảng trắng trong tên:

listOfNames="RA
RB
R C
RD"

for databaseName in $listOfNames  # Note: No quotes
do
  echo "$databaseName"  # (i.e. do action / processing of $databaseName here...)
done

# Outputs
# RA
# RB
# R
# C
# RD

Ghi chú

  1. Trong ví dụ thứ hai, sử dụng listOfNames="RA RB R C RD"có cùng một đầu ra.

Các cách khác để mang lại dữ liệu bao gồm:

Đọc từ stdin

# line delimited (each databaseName is stored on a line)
while read databaseName
do
  echo "$databaseName"  # i.e. do action / processing of $databaseName here...
done # <<< or_another_input_method_here
  1. dấu phân cách trường bash IFS "phân tách trường" [ 1 ] có thể được chỉ định trong tập lệnh để cho phép các khoảng trắng khác (ví dụ IFS='\n': hoặc cho MacOS IFS='\r')
  2. Tôi cũng thích câu trả lời được chấp nhận :) - Tôi đã bao gồm các đoạn này như những cách hữu ích khác cũng trả lời câu hỏi.
  3. Bao gồm #!/bin/bashở đầu tập tin kịch bản chỉ ra môi trường thực thi.
  4. Tôi đã mất nhiều tháng để tìm ra cách viết mã này một cách đơn giản :)

Các nguồn khác ( trong khi đọc vòng lặp )


Điều này tạo ấn tượng rằng eol được sử dụng làm dấu tách chuỗi và do đó, khoảng trắng được cho phép trong chuỗi. Tuy nhiên, các chuỗi có khoảng trắng được phân tách thành các chuỗi con, điều này rất rất xấu. Tôi nghĩ rằng câu trả lời này stackoverflow.com/a/23561892/1083704 là tốt hơn.
Val

1
@Val, tôi đã thêm nhận xét mã với một tham chiếu đến IFS. (Đối với mọi người, hãy IFScho phép một người chỉ định một dấu phân cách cụ thể, cho phép các khoảng trắng khác được bao gồm trong các chuỗi mà không bị tách thành các chuỗi con).
dùng2533809

7
Điều này không làm việc cho tôi. $databaseNamechỉ chứa toàn bộ danh sách, do đó chỉ thực hiện một lần lặp duy nhất.
AlikElzin-kilaka

@ AlikElzin-kilaka Câu trả lời của tôi dưới đây giải quyết vấn đề này để vòng lặp được chạy cho mọi dòng của chuỗi.
Jamie

42

Bạn có thể sử dụng cú pháp của ${arrayName[@]}

#!/bin/bash
# declare an array called files, that contains 3 values
files=( "/etc/passwd" "/etc/group" "/etc/hosts" )
for i in "${files[@]}"
do
    echo "$i"
done

31

Ngạc nhiên là chưa có ai đăng cái này - nếu bạn cần các chỉ số của các phần tử trong khi bạn đang lặp qua mảng, bạn có thể làm điều này:

arr=(foo bar baz)

for i in ${!arr[@]}
do
    echo $i "${arr[i]}"
done

Đầu ra:

0 foo
1 bar
2 baz

Tôi thấy điều này thanh lịch hơn nhiều so với kiểu vòng lặp "truyền thống" ( for (( i=0; i<${#arr[@]}; i++ ))).

( ${!arr[@]}$ikhông cần được trích dẫn vì chúng chỉ là những con số; một số người sẽ đề nghị trích dẫn chúng bằng mọi cách, nhưng đó chỉ là sở thích cá nhân.)


2
Đây thực sự nên là câu trả lời được lựa chọn. 1: Nó đơn giản và dễ đọc. 2: Nó xử lý khoảng trắng một cách chính xác, không có IFS vô nghĩa nào cản trở. 3: Nó xử lý các mảng thưa thớt một cách chính xác.
mrm

20

Điều này cũng dễ đọc:

FilePath=(
    "/tmp/path1/"    #FilePath[0]
    "/tmp/path2/"    #FilePath[1]
)

#Loop
for Path in "${FilePath[@]}"
do
    echo "$Path"
done

4
Cái này rõ ràng và hoạt động với tôi (bao gồm cả không gian và thay thế biến trong các phần tử mảng FilePath) chỉ khi tôi đặt biến IFS chính xác trước định nghĩa mảng FilePath: IFS=$'\n' Điều này cũng có thể hoạt động cho các giải pháp khác trong kịch bản này.
Alan Forsyth

8

Mảng ẩn cho tập lệnh hoặc hàm:

Ngoài câu trả lời đúng của anubhava : Nếu cú ​​pháp cơ bản cho vòng lặp là:

for var in "${arr[@]}" ;do ...$var... ;done

có một trường hợp đặc biệt trong:

Khi chạy một kịch bản hay một chức năng, lập luận thông qua tại dòng lệnh sẽ được gán cho $@biến mảng, bạn có thể truy cập bằng cách $1, $2, $3, và vân vân.

Điều này có thể được xác định (để kiểm tra) bởi

set -- arg1 arg2 arg3 ...

Một vòng lặp trên mảng này có thể được viết đơn giản:

for item ;do
    echo "This is item: $item."
  done

Lưu ý rằng công việc dành riêng inkhông có mặt và không có tên mảng quá!

Mẫu vật:

set -- arg1 arg2 arg3 ...
for item ;do
    echo "This is item: $item."
  done
This is item: arg1.
This is item: arg2.
This is item: arg3.
This is item: ....

Lưu ý rằng điều này giống với

for item in "$@";do
    echo "This is item: $item."
  done

Sau đó thành một kịch bản :

#!/bin/bash

for item ;do
    printf "Doing something with '%s'.\n" "$item"
  done

Lưu này trong một kịch bản myscript.sh, chmod +x myscript.shthì

./myscript.sh arg1 arg2 arg3 ...
Doing something with 'arg1'.
Doing something with 'arg2'.
Doing something with 'arg3'.
Doing something with '...'.

Tương tự trong một chức năng :

myfunc() { for item;do cat <<<"Working about '$item'."; done ; }

Sau đó

myfunc item1 tiem2 time3
Working about 'item1'.
Working about 'tiem2'.
Working about 'time3'.

7
listOfNames="db_one db_two db_three"
for databaseName in $listOfNames
do
  echo $databaseName
done

hoặc chỉ

for databaseName in db_one db_two db_three
do
  echo $databaseName
done

7

Cách đơn giản :

arr=("sharlock"  "bomkesh"  "feluda" )  ##declare array

len=${#arr[*]}  # it returns the array length

#iterate with while loop
i=0
while [ $i -lt $len ]
do
    echo ${arr[$i]}
    i=$((i+1))
done


#iterate with for loop
for i in $arr
do
  echo $i
done

#iterate with splice
 echo ${arr[@]:0:3}

6

Mảng khai báo không hoạt động cho shell Korn. Sử dụng ví dụ dưới đây cho vỏ Korn:

promote_sla_chk_lst="cdi xlob"

set -A promote_arry $promote_sla_chk_lst

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

2
thử mã tô sáng trong trình chỉnh sửa để làm cho mã của bạn trông đẹp.
bồ câu

5
Tốt để biết, nhưng câu hỏi này là về bash.
Brad Koch

1
Rất nhiều lỗi ở đây. Không thể có danh sách mục có khoảng trắng, không thể có danh sách mục có ký tự toàn cầu. for i in ${foo[*]}về cơ bản luôn luôn là điều sai - for i in "${foo[@]}"là hình thức bảo tồn ranh giới của danh sách ban đầu và ngăn chặn sự mở rộng toàn cầu. Và tiếng vang cần phải cóecho "$i"
Charles Duffy

4

Điều này tương tự như câu trả lời của user2533809, nhưng mỗi tệp sẽ được thực thi dưới dạng một lệnh riêng.

#!/bin/bash
names="RA
RB
R C
RD"

while read -r line; do
    echo line: "$line"
done <<< "$names"

3

Nếu bạn đang sử dụng shell Korn, có " set -A databaseName ", khác là " khai báo -a databaseName "

Để viết một kịch bản làm việc trên tất cả các shell,

 set -A databaseName=("db1" "db2" ....) ||
        declare -a databaseName=("db1" "db2" ....)
# now loop 
for dbname in "${arr[@]}"
do
   echo "$dbname"  # or whatever

done

Nó nên được làm việc trên tất cả các vỏ.


3
Không, nó không: $ bash --versionGNU bash, phiên bản 4.3.33 (0) -release (amd64-portbld-freebsd10.0) $ set -A databaseName=("db1" "db2" ....) || declare -a databaseName=("db1" "db2" ....)bash: lỗi cú pháp gần mã thông báo bất ngờ `('
Bernie Reiter

2

Thử cái này. Nó đang làm việc và thử nghiệm.

for k in "${array[@]}"
do
    echo $k
done

# For accessing with the echo command: echo ${array[0]}, ${array[1]}

3
Điều này không thực sự hoạt động chính xác. Hãy thử array=( "hello world" ), hoặc arrray=( "*" ); trong trường hợp đầu tiên, nó sẽ in helloworldriêng rẽ, trong lần thứ hai, nó sẽ in một danh sách các tệp thay vì*
Charles Duffy

6
... Trước khi gọi một cái gì đó "được thử nghiệm" trong vỏ, hãy chắc chắn kiểm tra các trường hợp góc, trong đó khoảng trắng và khoảng trống đều nằm trong số đó.
Charles Duffy

2

Dòng đầu tiên có thể có của mỗi tập lệnh Bash / phiên:

say() { for line in "${@}" ; do printf "%s\n" "${line}" ; done ; }

Sử dụng ví dụ:

$ aa=( 7 -4 -e ) ; say "${aa[@]}"
7
-4
-e

Có thể xem xét: echophiên dịch -elà tùy chọn ở đây


2

Vòng lặp đơn,

 declare -a listOfNames=('db_a' 'db_b' 'db_c')
 for databaseName in ${listOfNames[@]}; do echo $databaseName; done;

bạn sẽ nhận được một đầu ra như thế này,

db_a
db_b
db_c

2

Những gì tôi thực sự cần cho việc này là một cái gì đó như thế này:

for i in $(the_array); do something; done

Ví dụ:

for i in $(ps -aux | grep vlc  | awk '{ print $2 }'); do kill -9 $i; done

(Sẽ giết tất cả các quy trình với vlc trong tên của họ)


1

Tôi lặp qua một loạt các dự án của tôi để git pullcập nhật:

#!/bin/sh
projects="
web
ios
android
"
for project in $projects do
    cd  $HOME/develop/$project && git pull
end

7
Mặc dù đoạn mã này có thể giải quyết câu hỏi, bao gồm một lời giải thích thực sự giúp cải thiện chất lượng bài đăng của bạn. Hãy nhớ rằng bạn đang trả lời câu hỏi cho độc giả trong tương lai và những người đó có thể không biết lý do cho đề xuất mã của bạn. Xin vui lòng cố gắng không làm đông mã của bạn với các bình luận giải thích, điều này làm giảm khả năng đọc của cả mã và các giải thích!
chèo thuyền kayak
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.