Làm thế nào tôi có thể mở rộng một biến được trích dẫn thành không có gì nếu nó trống?


21

Nói rằng tôi có một kịch bản đang làm:

some-command "$var1" "$var2" ...

Và, trong trường hợp var1trống, tôi muốn thay thế nó bằng không có gì thay vì chuỗi rỗng, để lệnh được thực thi là:

some-command "$var2" ...

và không:

some-command '' "$var2" ...

Có cách nào đơn giản hơn là kiểm tra biến và có điều kiện bao gồm nó không?

if [ -n "$1" ]; then
    some-command "$var1" "$var2" ...
    # or some variant using arrays to build the command
    # args+=("$var1")
else
    some-command "$var2" ...
fi

Có một sự thay thế tham số hơn có thể mở rộng thành không có gì trong bash, zsh, hoặc tương tự? Tôi vẫn có thể muốn sử dụng Globing trong phần còn lại của các đối số, vì vậy, vô hiệu hóa điều đó và bỏ qua biến không phải là một tùy chọn.


Tôi biết tôi đã thấy và có lẽ đã sử dụng nó trước đây, nhưng nó tỏ ra khó tìm kiếm. Bây giờ Michael đã hiển thị cú pháp, tôi đã nhớ nơi tôi lần đầu tiên nhìn thấy nó đủ nhanh: unix.stackexchange.com/a/269549/70524 , unix.stackexchange.com/q/68484/70524
muru

Nếu bạn biết đó là một loại thay thế tham số, tại sao bạn không xem xét phần mở rộng tham số của mantrang? (-;
Philippos

1
@Philippos Tôi không biết nó là gì vào thời điểm đó, chỉ là tôi đã thấy hoặc sử dụng nó trước đây. Được biết đến và biết chưa biết. :(
muru

1
điểm cookie bổ sung để đề cập bằng cách sử dụng một mảng để giữ các đối số trong chính câu hỏi.
ilkkachu

Câu trả lời:


29

Các shell tương thích PosixBash có ${parameter:+word} :

Nếu tham số không được đặt hoặc null, null sẽ được thay thế; mặt khác, việc mở rộng từ (hoặc một chuỗi trống nếu từ bị bỏ qua) sẽ được thay thế.

Vì vậy, bạn chỉ có thể làm:

${var1:+"$var1"}

và đã var1được kiểm tra và "$var1"được sử dụng nếu nó được đặt và không trống (với các quy tắc trích dẫn kép thông thường). Nếu không, nó mở rộng thành không có gì. Lưu ý rằng chỉ có phần bên trong được trích dẫn ở đây, không phải toàn bộ.

Điều tương tự cũng hoạt động trong zsh. Bạn phải lặp lại biến, vì vậy nó không lý tưởng, nhưng nó hoạt động chính xác như bạn muốn.

Nếu bạn muốn một biến set-but-blank mở rộng thành một đối số trống, ${var1+"$var1"}thay vào đó hãy sử dụng .


1
Đủ tôt cho tôi. Vì vậy, trong này, toàn bộ điều không được trích dẫn, chỉ có wordmột phần là.
muru

1
Khi câu hỏi yêu cầu "bash, zsh hoặc tương tự" (và cho kho lưu trữ Q & A), tôi đã chỉnh sửa để phản ánh rằng đây là một tính năng tích hợp. Thậm chí, nếu bạn thêm thẻ / bash vào câu hỏi sau bình luận của tôi. (-;
Philippos

2
Tôi đã tự do chỉnh sửa trong sự khác biệt giữa :++.
ilkkachu

Cảm ơn rất nhiều cho một giải pháp tuân thủ POSIX! Tôi cố gắng sử dụng sh/ dashcho các kịch bản chokepoint tiềm năng, vì vậy tôi luôn đánh giá cao nó khi ai đó chỉ ra cách thức một điều có thể xảy ra mà không cần dùng đến Bash.
JamesTheAwgieDude

Tôi đã tìm kiếm hiệu ứng ngược lại (Mở rộng sang thứ khác nếu nó bắt đầu là null). Hóa ra logic này có thể được đảo ngược bằng cách thay đổi + thành a - như trong: $ {blank_var: -replocation}
Alex Jansen

5

Đó là những gì zshlàm theo mặc định khi bạn bỏ qua các trích dẫn:

some-command $var1 $var2

Trên thực tế, lý do duy nhất tại sao bạn vẫn cần trích dẫn trong zsh xung quanh việc mở rộng tham số là để tránh hành vi đó (loại bỏ trống) vì zshkhông có các vấn đề khác ảnh hưởng đến các shell khác khi bạn không trích dẫn mở rộng tham số (chia tách ẩn + toàn cầu) .

Bạn có thể làm tương tự với các shell giống như POSIX khác nếu bạn tắt tính năng phân tách và toàn cầu:

(IFS=; set -o noglob; some-command $var1 $var2)

Bây giờ, tôi lập luận rằng nếu biến của bạn có thể có giá trị 0 hoặc 1, thì đó phải là một mảng chứ không phải là biến vô hướng và sử dụng:

some-command "${var1[@]}" "${var2[@]}"

Và sử dụng var1=(value)khi nào var1để chứa một giá trị, var1=('')khi nào nó chứa một giá trị trống và var1=()khi nào nó không chứa giá trị.


0

Tôi đã sử dụng rsync trong một tập lệnh bash bắt đầu lệnh có hoặc không có -nchuyển đổi chạy khô. Nó chỉ ra rằng rsync và một số lệnh gnu lấy ''làm đối số đầu tiên hợp lệ và hành động khác với khi nó không có ở đó.

Điều này mất khá nhiều thời gian để gỡ lỗi vì các tham số null gần như hoàn toàn vô hình.

Một người nào đó trong danh sách rsync đã chỉ cho tôi một cách để tránh vấn đề này đồng thời cũng đơn giản hóa rất nhiều mã hóa của tôi. Nếu tôi hiểu chính xác, đây là một biến thể của đề xuất cuối cùng của @ Stéphane Chazelas.

Xây dựng các đối số lệnh của bạn trong một số biến riêng biệt. Chúng có thể được đặt theo bất kỳ thứ tự hoặc logic nào phù hợp với vấn đề.

Sau đó, cuối cùng, sử dụng các biến để xây dựng một mảng với mọi thứ ở đúng vị trí của nó và sử dụng nó làm đối số cho lệnh thực tế.

Bằng cách này, lệnh chỉ được ban hành tại một nơi trong mã thay vì được lặp lại cho mỗi biến thể của các đối số.

Bất kỳ biến nào trống chỉ biến mất bằng phương pháp này.

Tôi biết sử dụng eval là rất khó chịu. Tôi không nhớ tất cả các chi tiết, nhưng dường như tôi cần nó để mọi thứ hoạt động theo cách này - một việc cần làm với việc xử lý các tham số với khoảng trắng được nhúng.

Thí dụ:

dry_run=''
if [[ it is a test run ]]
then
  dry_run='-n'
fi
...
rsync_options=(
  ${dry_run}
  -avushi
  ${delete}
  ${excludes}
  --stats
  --progress
)
...
eval rsync "${rsync_options[@]}" ...

Đó là một cách tồi tệ hơn để làm những gì tôi mô tả trong câu hỏi (xây dựng một loạt các đối số có điều kiện). Bằng cách để các biến không được trích dẫn, ai biết được vấn đề gì bạn đang để mình mở.
muru

@muru Bạn nói đúng, nhưng tôi vẫn cần một cái gì đó như thế này. Tôi hoàn toàn không thấy cách khắc phục bằng các kỹ thuật từ các câu trả lời khác. Sẽ thật tuyệt khi thấy một đoạn mã được thực hiện đúng cách kết hợp tất cả lại với nhau. Tôi sẽ chơi với tùy chọn cuối cùng của Stephane vì điều đó có thể làm những gì tôi muốn.
Joe

Giống như dán.ubfox.com/26383847 ?
muru

Chỉ cần trở lại với điều này. Cảm ơn ví dụ. Nó có ý nghĩa. Tôi sẽ làm việc với nó.
Joe

Tôi có một trường hợp sử dụng tương tự ở đây, nhưng bạn có thể làm một cái gì đó như thế này: if ["$ khô" == "true"]; sau đó khô = "- n"; fi ... vì vậy nếu biến khô là đúng, bạn biến nó thành -n, và sau đó bạn xây dựng lệnh rsync như bình thường và có nó như thế này: rsync $ {Dry1: + "$ Dry1"} ... nếu nó không sẽ không có gì xảy ra, nếu không nó sẽ trở thành - trong lệnh của bạn
Freedo
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.