Dấu ngoặc trong số học expr: 3 * (2 + 1)


60

expr dường như không thích dấu ngoặc đơn (được sử dụng trong toán học để ưu tiên toán tử rõ ràng):

expr 3 * (2 + 1)
bash: syntax error near unexpected token `('

Làm thế nào để thể hiện ưu tiên của nhà điều hành trong bash?

Câu trả lời:


40

Một cách khác để sử dụng letbash dựng sẵn:

$ let a="3 * (2 + 1)"
$ printf '%s\n' "$a"
9

Ghi chú

Như @ Stéphane Chazelas đã chỉ ra , trong bashbạn nên sử dụng ((...))để làm số học hơn exprhoặc letcho mức độ dễ đọc.

Đối với tính di động, sử dụng $((...))như câu trả lời @Bernhard .


1
+1 Thậm chí dễ đọc hơn! Tôi đã đăng câu hỏi của mình + câu trả lời chỉ nghĩ rằng nó sẽ hữu ích cho những người dùng Linux của tôi, nhưng bây giờ tôi nhận được rất nhiều lợi ích từ các câu trả lời khác :-)
Nicolas Raoul

3
Không có lý do để sử dụng let. Nó không phải là bất kỳ tiêu chuẩn hoặc di động hơn (( a = 3 * (2 + 1) ))(cả hai đến từ kshvà chỉ có sẵn trong ksh, bash và zsh) và nó ít dễ đọc hoặc dễ trích dẫn. Sử dụng a=$((3 * (2 + 1)))để được xách tay.
Stéphane Chazelas

2
Tôi không nói sai, tôi chỉ nói rằng nó không nên được sử dụng vì có những lựa chọn thay thế tốt hơn (một cho tính dễ đọc ((a = 3 * (2 + 1) )), một cho tính di động a=$((3 * (2 + 1)))), vì vậy đó không phải là một lưu ý chống lại bạn hoặc câu trả lời của bạn mà là câu trả lời được chọn và cầu thủ ghi bàn hàng đầu.
Stéphane Chazelas

@ StéphaneChazelas: Cập nhật câu trả lời của tôi!
cuonglm

Tôi đã luôn luôn sử dụng a=1 $[a+2]hoặc a=1 b=2 $[a+b]. Là lý do của họ để tránh cú pháp đó?
Gordon

74

Bạn có thể sử dụng mở rộng số học thay thế.

echo "$(( 3 * ( 2 + 1 ) ))"
9

Theo ý kiến ​​cá nhân của tôi, điều này có vẻ tốt hơn một chút so với sử dụng expr.

Từ man bash

Mở rộng số học Mở rộng số học cho phép đánh giá biểu thức số học và thay thế kết quả. Định dạng để mở rộng số học là:

         $((expression))

Biểu thức được xử lý như thể nó nằm trong dấu ngoặc kép, nhưng trích dẫn kép bên trong dấu ngoặc đơn không được xử lý đặc biệt. Tất cả các mã thông báo trong biểu thức trải qua mở rộng tham số, mở rộng chuỗi, thay thế lệnh và xóa trích dẫn. Mở rộng số học có thể được lồng nhau.

Việc đánh giá được thực hiện theo các quy tắc được liệt kê dưới đây trong ĐÁNH GIÁ ARITHMETIC. Nếu biểu thức không hợp lệ, bash sẽ in một thông báo cho biết lỗi và không có sự thay thế nào xảy ra.


1
Ngoài khả năng đọc, nó cũng không yêu cầu phải tạo ra một quy trình bổ sung để thực hiện số học; nó được xử lý bởi chính vỏ.
chepner

Lưu ý rằng trong các vỏ POSIX, nó có thể bị tách từ, vì vậy đây là một thói quen tốt để trích dẫn nó trong ngữ cảnh danh sách.
Stéphane Chazelas

Khi tôi thử cái này ở bash shell, tôi nhận được name Tên biến bất hợp pháp. "
chúa tể

40

Không có lý do để sử dụng exprcho số học trong các lớp vỏ hiện đại.

POSIX định nghĩa $((...))toán tử mở rộng. Vì vậy, bạn có thể sử dụng nó trong tất cả các hệ vỏ tương thích POSIX ( shtất cả các ứng dụng Unix hiện đại, dash, bash, yash, mksh, zsh, posh, ksh ...).

a=$(( 3 * (2 + 1) ))
a=$((3*(2+1)))

kshcũng đã giới thiệu một letnội dung được truyền cùng loại biểu thức số học, không mở rộng thành thứ gì đó nhưng trả về trạng thái thoát dựa trên việc biểu thức có giải quyết thành 0 hay không, như trong expr:

if let 'a = 3 * (2 + 1)'; then
  echo "$a is non-zero"
fi

Tuy nhiên, vì trích dẫn làm cho nó khó xử và không dễ đọc (không đến mức tương tự như exprtất nhiên), kshcũng giới thiệu một ((...))hình thức thay thế:

if (( a = 3 * (2 + 1) )) && (( 3 > 1 )); then
  echo "$a is non-zero and 3 > 1"
fi
((a+=2))

cái nào dễ đọc hơn và nên được sử dụng thay thế.

let((...))chỉ có sẵn trong ksh, zshbash. Các $((...))cú pháp nên được ưa thích nếu tính di động với cái vỏ khác là cần thiết, exprchỉ cần thiết cho vỏ trước POSIX Bourne-tương tự (thường là Bourne shell hoặc phiên bản đầu tiên của vỏ Almquist).

Ở mặt trước không phải là Bourne, có một vài shell với toán tử số học tích hợp:

  • csh/ tcsh(thực sự là vỏ Unix đầu tiên có tích hợp đánh giá số học):

    @ a = 3 * (2 + 1)
  • akanga(dựa trên rc)

    a = $:'3 * (2 + 1)'
  • như một ghi chú lịch sử, phiên bản gốc của vỏ Almquist, như được đăng trên usenet vào năm 1989 đã có một bản exprdựng (thực sự được hợp nhất với test), nhưng nó đã bị xóa sau đó.


Tôi học được điều gì đó mới mỗi ngày từ bạn, Stéphane. Tôi rất đánh giá cao kiến ​​thức vỏ POSIX của bạn!
MattBianco

Thế còn : $((a = a*2))?
Arthur2e5

Nếu tôi có một điểm nổi thì sao? Biểu thức của tôi là a = $ ((-14 + 0,2 * (1 + 2 + 3))). Mã thông báo lỗi là ".2 * (1 + 2 + 3)"
Blaise

@Blaise, sau đó bạn sẽ cần một hệ vỏ hỗ trợ các điểm nổi $((...))như zsh, ksh93 hoặc yash.
Stéphane Chazelas

16

exprlà một lệnh bên ngoài, nó không phải là cú pháp shell đặc biệt. Do đó, nếu bạn muốn exprxem các ký tự đặc biệt của shell, bạn cần bảo vệ chúng khỏi phân tích shell bằng cách trích dẫn chúng. Hơn nữa, exprcần mỗi số và toán tử được truyền dưới dạng một tham số riêng. Do vậy:

expr 3 \* \( 2 + 1 \)

Trừ khi bạn đang làm việc trên một hệ thống unix cổ từ những năm 1970 hoặc 1980, có rất ít lý do để sử dụng expr. Vào thời xưa, shell không có cách tích hợp để thực hiện số học và exprthay vào đó bạn phải gọi tiện ích. Tất cả các vỏ POSIX đều có số học tích hợp thông qua cú pháp mở rộng số học .

echo "$((3 * (2 + 1)))"

Cấu trúc $((…))mở rộng đến kết quả của biểu thức số học (được viết bằng số thập phân). Bash, giống như hầu hết các shell, chỉ hỗ trợ modulo số học 2 64 (hoặc modulo 2 32 cho các phiên bản cũ hơn của bash và một số shell khác trên các máy 32 bit).

Bash cung cấp một cú pháp tiện lợi bổ sung khi bạn muốn thực hiện các bài tập hoặc để kiểm tra xem một biểu thức có bằng 0 hay không nhưng không quan tâm đến kết quả. Cấu trúc này cũng tồn tại trong ksh và zsh nhưng không phải trong sh đơn giản.

((x = 3 * (2+1)))
echo "$x"
if ((x > 3)); then 

Ngoài số học số nguyên, exprcung cấp một vài hàm thao tác chuỗi. Những thứ này cũng được thay thế bởi các tính năng của shell POSIX, ngoại trừ một: expr STRING : REGEXPkiểm tra xem chuỗi có khớp với biểu thức chính quy không. Một vỏ POSIX không thể làm điều này mà không có các công cụ bên ngoài, nhưng bash có thể với [[ STRING =~ REGEXP ]](với một cú pháp regrec khácexprlà một công cụ cổ điển và sử dụng BRE, bash sử dụng ERE).

Trừ khi bạn đang duy trì các tập lệnh chạy trên các hệ thống 20 năm tuổi, bạn không cần biết rằng nó exprđã từng tồn tại. Sử dụng số học shell.


expr foo : '\(.\)'cũng không trích xuất văn bản. bash's BASH_REMATCHđạt được một cái gì đó tương tự. Nó cũng thực hiện so sánh chuỗi, điều mà POSIX [không làm (mặc dù người ta có thể tưởng tượng ra các cách sử dụng sortcho điều đó).
Stéphane Chazelas

gạch dưới làm chỗ giữ cú pháp --- bạn là Schemer, @Giles? :]
RubyTuesdayDONO

1
@RubyTuesdayDONO Tôi không sử dụng dấu gạch dưới ở đây. Bạn đang đọc sai ULL 2026 HORIZONTAL ELLIPSIS? Nếu vậy, hãy thử sử dụng một phông chữ lớn hơn.
Gilles 'SO- ngừng trở nên xấu xa'

@Giles - okay, vâng, nó chỉ trông giống như một dấu gạch dưới vì kích thước phông chữ của tôi. đối với tôi, "Schemer" là một bổ sung, và nó không giống như dấu chấm lửng so với dấu gạch dưới thay đổi ý nghĩa dù sao thì không cần phải lén lút về nó: /
RubyTuesdayDONO

12

Sử dụng dấu ngoặc đơn với dấu ngoặc kép:

expr 3 '*' '(' 2 '+' 1 ')'
9

Các trích dẫn ngăn bash diễn giải dấu ngoặc đơn dưới dạng cú pháp bash.


2
Những gì Nicolas minh họa nhưng không giải thích là các mã thông báo trên exprdòng lệnh phải được phân tách bằng khoảng trắng; vì thế; ví dụ, expr 3 "*" "(2" "+" "1)" sẽ không hoạt động . (Ngoài ra, BTW, có lẽ bạn không cần trích dẫn +.)
G-Man

Các dấu ngoặc đơn không có từ khóa như while[[, chúng là cú pháp. Nếu chúng là từ khóa, chúng sẽ không được hiểu như vậy trong các đối số lệnh. Bạn cần trích dẫn để bash không phân tích cú pháp mà thay vào đó sẽ thấy một chuỗi ký tự.
Gilles 'SO- ngừng trở nên xấu xa'

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.