Câu hỏi Làm thế nào để tôi lặp qua một loạt các số được xác định bởi các biến trong Bash?


Làm thế nào để tôi lặp qua một loạt các số trong Bash khi phạm vi được cho bởi một biến?

Tôi biết tôi có thể làm điều này (được gọi là "biểu thức trình tự" trong Bash tài liệu):

 for i in {1..5}; do echo $i; done

Cung cấp cho:

1
  2
  3
  4
  5

Tuy nhiên, làm thế nào tôi có thể thay thế một trong hai điểm cuối phạm vi bằng một biến? Điều này không hoạt động:

END=5
for i in {1..$END}; do echo $i; done

Bản in nào:

{1..5}


1058
2017-10-04 01:38


gốc


Hi all, các thông tin và gợi ý tôi đã đọc ở đây là tất cả thực sự hữu ích. Tôi nghĩ tốt nhất là tránh sử dụng seq. Lý do là một số kịch bản cần phải di động và phải chạy trên nhiều hệ thống Unix khác nhau, trong đó một số lệnh có thể không có mặt. Chỉ cần làm một ví dụ, seq không có mặt mặc định trên các hệ thống FreeBSD.
Liên quan đến discusions: bash for loop: một loạt các số và unix.stackexchange.com - Trong bash, có thể sử dụng biến số nguyên trong điều khiển vòng lặp của vòng lặp for không? - blong
Tôi không nhớ kể từ phiên bản Bash nào chính xác nhưng lệnh này cũng hỗ trợ các số 0. Mà đôi khi thực sự hữu ích. Chỉ huy for i in {01..10}; do echo $i; done sẽ cung cấp cho các con số như 01, 02, 03, ..., 10. - topr
Đối với những người như tôi, những người chỉ muốn lặp qua các chỉ số của một mảng, cách bash sẽ là: myarray=('a' 'b' 'c'); for i in ${!myarray[@]}; do echo $i; done (lưu ý dấu chấm than). Nó cụ thể hơn câu hỏi ban đầu, nhưng có thể giúp. Xem bash thông số mở rộng - PlasmaBinturong


Các câu trả lời:


for i in $(seq 1 $END); do echo $i; done

chỉnh sửa: Tôi thích seq qua các phương pháp khác vì tôi thực sự có thể nhớ nó;)


1190
2017-10-04 01:41



seq liên quan đến việc thực hiện một lệnh bên ngoài mà thường làm chậm mọi thứ. Điều này có thể không quan trọng nhưng nó trở nên quan trọng nếu bạn đang viết một kịch bản để xử lý nhiều dữ liệu. - paxdiablo
Chỉ tốt cho một lót. Pax của giải pháp là tốt quá, nhưng nếu hiệu suất thực sự là một mối quan tâm tôi sẽ không được sử dụng một kịch bản shell. - eschercycle
seq được gọi chỉ một lần để tạo ra các số. exec () 'ing nó không nên có ý nghĩa trừ khi vòng lặp này là bên trong một vòng lặp chặt chẽ. - Javier
Lệnh bên ngoài không thực sự có liên quan: nếu bạn đang lo lắng về chi phí của việc chạy các lệnh bên ngoài, bạn không muốn sử dụng các kịch bản lệnh shell, nhưng nói chung trên unix thì chi phí thấp. Tuy nhiên, có vấn đề sử dụng bộ nhớ nếu END cao. - Mark Baker
Lưu ý rằng seq $END sẽ đủ, như mặc định là bắt đầu từ 1. Từ man seq: "Nếu FIRST hoặc INCREMENT bị bỏ qua, giá trị mặc định là 1". - fedorqui


Các seq phương pháp là đơn giản nhất, nhưng Bash đã được xây dựng trong số học đánh giá.

END=5
for ((i=1;i<=END;i++)); do
    echo $i
done
# ==> outputs 1 2 3 4 5 on separate lines

Các for ((expr1;expr2;expr3)); xây dựng các công trình giống như for (expr1;expr2;expr3) bằng ngôn ngữ C và các ngôn ngữ tương tự và giống như ngôn ngữ khác ((expr)) trường hợp, Bash đối xử với chúng như số học.


310
2017-10-04 21:43



Một người không ngừng học hỏi. Tôi sẽ thêm một typeset -i i END Tuy nhiên. Trong thời gian trước bash (tức là ksh) nó tạo ra sự khác biệt, nhưng máy tính chậm hơn nhiều. - tzot
Bằng cách này tránh được phí trên bộ nhớ của một danh sách lớn và sự phụ thuộc vào seq. Sử dụng nó! - bobbogo
giải pháp tốt nhất cho tôi khi tôi cần nó cho bash trên hệ điều hành khác nhau - Hachi
@MarinSagovac This làm làm việc và không có lỗi cú pháp. Bạn có chắc chắn vỏ của bạn là Bash? - gniourf_gniourf
@MarinSagovac Hãy chắc chắn để làm cho #!/bin/bash dòng đầu tiên của tập lệnh. wiki.ubuntu.com/… - Melebius


thảo luận

Sử dụng seq là tốt, như Jiaaro đề nghị. Pax Diablo đã đề xuất một vòng lặp Bash để tránh gọi một tiến trình con, với lợi thế bổ sung là thân thiện với bộ nhớ hơn nếu $ END quá lớn. Zathrus phát hiện một lỗi điển hình trong việc thực hiện vòng lặp, và cũng gợi ý rằng kể từ đó i là một biến văn bản, các số chuyển đổi liên tục thành số và số fro được thực hiện với tốc độ chậm liên quan.

số học số nguyên

Đây là phiên bản cải tiến của vòng lặp Bash:

typeset -i i END
let END=5 i=1
while ((i<=END)); do
    echo $i
    …
    let i++
done

Nếu điều duy nhất chúng tôi muốn là echothì chúng ta có thể viết echo $((i++)).

vô ích đã dạy tôi điều gì đó: Bash cho phép for ((expr;expr;expr)) cấu trúc. Vì tôi chưa bao giờ đọc toàn bộ trang của người đàn ông cho Bash (như tôi đã làm với vỏ Korn (ksh) trang người đàn ông, và đó là một thời gian dài trước đây), tôi đã bỏ lỡ điều đó.

Vì thế,

typeset -i i END # Let's be explicit
for ((i=1;i<=END;++i)); do echo $i; done

dường như là cách hiệu quả nhất về bộ nhớ (nó sẽ không cần thiết để cấp phát bộ nhớ để tiêu thụ seq's đầu ra, mà có thể là một vấn đề nếu END là rất lớn), mặc dù có lẽ không phải là "nhanh nhất".

câu hỏi ban đầu

eschercycle lưu ý rằng {một..b} Ký pháp Bash chỉ hoạt động với các chữ; đúng, theo hướng dẫn của Bash. Người ta có thể vượt qua chướng ngại vật này với một cái duy nhất (bên trong) fork() không có exec() (như trường hợp gọi điện thoại seq, mà là một hình ảnh khác đòi hỏi một ngã ba + exec):

for i in $(eval echo "{1..$END}"); do

Cả hai eval và echo là các nội trang Bash, nhưng fork() là cần thiết cho việc thay thế lệnh ( $(…) xây dựng).


160
2017-10-04 02:38



Hạn chế duy nhất đối với vòng lặp kiểu C là nó không thể sử dụng đối số dòng lệnh, khi chúng bắt đầu bằng "$". - karatedog
@karatedog: for ((i=$1;i<=$2;++i)); do echo $i; done trong một kịch bản hoạt động tốt cho tôi trên bash v.4.1.9, vì vậy tôi không thấy một vấn đề với các đối số dòng lệnh. Bạn có ý gì khác không? - tzot
Dường như giải pháp eval nhanh hơn được xây dựng trong C giống như: $ time for ((i = 1; i <= 100000; ++ i)); làm:; đã thực hiện 0m21.220s thực sự cho người dùng 0m19.763s sys 0m1.203s $ thời gian cho i bằng $ (eval echo "{1..100000}"); làm:; làm xong; 0m13.881s thực sự của người dùng 0m13.536s sys 0m0.152s - Marcin Zaluski
Đúng nhưng eval là ác... @MarcinZaluski time for i in $(seq 100000); do :; done nhanh hơn rất nhiều! - F. Hauri
Hiệu năng phải là nền tảng cụ thể vì phiên bản eval nhanh nhất trên máy của tôi. - Andrew Prock


Đây là lý do tại sao biểu thức ban đầu không hoạt động.

Từ người đàn ông bash:

Mở rộng cú đúp được thực hiện trước   bất kỳ mở rộng nào khác và bất kỳ   nhân vật đặc biệt cho người khác   mở rộng được bảo tồn trong   kết quả. Nó hoàn toàn là văn bản. Bash   không áp dụng bất kỳ cú pháp nào   giải thích cho bối cảnh của   việc mở rộng hoặc văn bản giữa   niềng răng.

Vì thế, mở rộng cú đúp là một cái gì đó được thực hiện sớm như một hoạt động macro thuần văn bản, trước mở rộng tham số.

Vỏ là các giống lai được tối ưu hóa cao giữa các bộ vi xử lý macro và các ngôn ngữ lập trình chính thức hơn. Để tối ưu hóa các trường hợp sử dụng điển hình, ngôn ngữ được thực hiện khá phức tạp và một số hạn chế được chấp nhận.

sự giới thiệu

Tôi sẽ đề nghị gắn bó với Posix1 Tính năng, đặc điểm. Điều này có nghĩa là sử dụng for i in <list>; do, nếu danh sách đã được biết, nếu không, hãy sử dụng while hoặc là seq, như trong:

#!/bin/sh

limit=4

i=1; while [ $i -le $limit ]; do
  echo $i
  i=$(($i + 1))
done
# Or -----------------------
for i in $(seq 1 $limit); do
  echo $i
done


1. Bash là một shell tuyệt vời và tôi sử dụng nó một cách tương tác, nhưng tôi không đặt các bash-isms vào các kịch bản của tôi. Các tập lệnh có thể cần một trình bao nhanh hơn, một trình bao an toàn hơn, một kiểu được nhúng nhiều hơn. Họ có thể cần phải chạy trên bất cứ điều gì được cài đặt như / bin / sh, và sau đó có tất cả các đối số tiêu chuẩn thông thường. Nhớ lại vỏ sò, hay còn gọi là bashdoor? 


81
2018-04-19 22:32



Tôi không có quyền lực, nhưng tôi sẽ di chuyển này khá một chút lên danh sách, trên tất cả các bash navel-gazing nhưng ngay lập tức sau khi C-phong cách cho vòng lặp và đánh giá số học. - mateor
Một hàm ý là việc mở rộng cú đúp không tiết kiệm nhiều bộ nhớ so với seq cho các phạm vi rộng. Ví dụ., echo {1..1000000} | wc tiết lộ rằng tiếng vang tạo ra 1 dòng, một triệu từ và 6,888,896 byte. Cố gắng seq 1 1000000 | wc mang lại một triệu dòng, một triệu từ và 6,888,896 byte và cũng nhanh hơn bảy lần, được đo bằng time chỉ huy. - George
Lưu ý: Tôi đã đề cập đến POSIX while phương pháp trước đây trong câu trả lời của tôi: stackoverflow.com/a/31365662/895245 Nhưng vui vì bạn đồng ý :-) - Ciro Santilli 新疆改造中心 六四事件 法轮功


Cách POSIX

Nếu bạn quan tâm đến tính di động, hãy sử dụng ví dụ từ tiêu chuẩn POSIX:

i=2
end=5
while [ $i -le $end ]; do
    echo $i
    i=$(($i+1))
done

Đầu ra:

2
3
4
5

Những thứ không phải POSIX:


43
2017-07-12 07:54



Chỉ có 4 upvotes trên câu trả lời này, đó là rất bất thường. Nếu điều này đã được đăng trên một số trang web liên kết tập hợp, xin vui lòng cho tôi một liên kết, cổ vũ. - Ciro Santilli 新疆改造中心 六四事件 法轮功
Trích dẫn đề cập đến x, không phải toàn bộ biểu thức. $((x + 1)) là tốt. - chepner
Mặc dù không di động và khác với GNU seq(BSD seq cho phép bạn thiết lập chuỗi kết thúc chuỗi với -t), FreeBSD và NetBSD cũng có seq kể từ 9.0 và 3.0, tương ứng. - Adrian Günter
@CiroSantilli @chepner $((x+1)) và $((x + 1)) phân tích cú pháp chính xác như nhau, như khi trình phân tích cú pháp mã hóa x+1 nó sẽ được chia thành 3 thẻ: x, +và 1. x không phải là mã thông báo số hợp lệ, nhưng đó là mã thông báo tên biến hợp lệ, x+ không phải, vì thế sự chia rẽ. + là mã thông báo toán tử số học hợp lệ, +1 không phải, vì vậy mã thông báo lại được tách ra ở đó. Và kể từ đó trở đi. - Adrian Günter


Một lớp khác của indirection:

for i in $(eval echo {1..$END}); do
    ∶

28
2018-03-14 19:51



+1: Ngoài ra, eval 'cho i trong {1 ..' $ END '}; làm ... 'eval có vẻ là cách tự nhiên để giải quyết vấn đề này. - William Pursell


Bạn có thể dùng

for i in $(seq $END); do echo $i; done

21
2017-10-04 01:41



Nó không liên quan đến việc thực hiện một lệnh bên ngoài cho mỗi lần lặp lại, chỉ một lần. Nếu thời gian khởi chạy một lệnh bên ngoài là một vấn đề, bạn đang sử dụng sai ngôn ngữ. - Mark Baker
+1 cho $() thay vì backticks - Dennis Williamson
Tại sao $ () tốt hơn ``? - Sqeaky
@ Sqeaky: bạn đã từng cố gắng làm tổ chưa `` cuộc gọi? -) - tzot
Vì vậy, là làm tổ trường hợp duy nhất mà vấn đề này? Tôi đã tự hỏi nếu có một sự khác biệt hiệu suất, hoặc một số tác dụng phụ kỹ thuật chưa biết? - Sqeaky


Nếu bạn đang sử dụng BSD / OS X, bạn có thể sử dụng jot thay vì seq:

for i in $(jot $END); do echo $i; done

18
2018-03-15 23:29



Điều này không hoạt động trên 10.8.x - Alan
Sử dụng 10.8.5 ở đây và nó hoạt động tốt. - SigmaX
jot và seq cả hai đều hoạt động với tôi trên OS X 10.11 - Jiaaro


Điều này hoạt động tốt trong bash:

END=5
i=1 ; while [[ $i -le $END ]] ; do
    echo $i
    ((i = i + 1))
done

13
2017-10-04 01:42



((i++)) công trinh - Dennis Williamson
echo $((i++)) hoạt động và kết hợp nó vào một dòng. - Bruno Bronosky
Điều này có phần mở rộng bash không cần thiết. Phiên bản POSIX: stackoverflow.com/a/31365662/895245 - Ciro Santilli 新疆改造中心 六四事件 法轮功
@ Ciro, kể từ khi câu hỏi cụ thể nói bash, và có một thẻ bash, tôi nghĩ rằng bạn có thể sẽ thấy rằng bash 'mở rộng' là nhiều hơn okay :-) - paxdiablo
@paxdiablo Tôi không có nghĩa là nó không chính xác, nhưng tại sao không được di động khi chúng ta có thể ;-) - Ciro Santilli 新疆改造中心 六四事件 法轮功


Nếu bạn cần nó tiền tố hơn bạn có thể như thế này

 for ((i=7;i<=12;i++)); do echo `printf "%2.0d\n" $i |sed "s/ /0/"`;done

sẽ mang lại lợi nhuận

07
08
09
10
11
12

11
2018-06-03 20:14