Cách thanh lịch để xây dựng một đường ống dựa trên giá trị trả về và không thoát mã?


8

Khi mã trạng thái là vô dụng, có cách nào để xây dựng một đường ống dựa trên đầu ra từ thiết bị xuất chuẩn không?

Tôi muốn câu trả lời không giải quyết trường hợp sử dụng mà là câu hỏi trong phạm vi kịch bản lệnh shell. Những gì tôi đang cố gắng làm là tìm gói cụ thể nhất có sẵn trong kho bằng cách đoán tên dựa trên mã quốc gia và ngôn ngữ.

Lấy ví dụ này,

  • $PACKAGE1=hunspell-en-zz
  • $PACKAGE2=hunspell-en

Dự đoán đầu tiên là phù hợp hơn nhưng nó có thể không tồn tại. Trong trường hợp này, tôi muốn trả về hunspell-en( $PACKAGE2) vì tùy chọn đầu tiên hunspell-en-zz( $PACKAGE1) không tồn tại.

đường ống của apt-cache

Lệnh apt-cachetrả về thành công (được xác định bởi shell là mã thoát 0) bất cứ khi nào lệnh có thể chạy (từ các tài liệu của apt-cache)

apt-cache trả về 0 khi hoạt động bình thường, lỗi thập phân 100.

Điều đó làm cho việc sử dụng lệnh trong một đường ống khó khăn hơn. Thông thường, tôi hy vọng gói tìm kiếm tương đương với 404 sẽ dẫn đến lỗi (như sẽ xảy ra với curlhoặc wget). Tôi muốn tìm kiếm để xem nếu một gói tồn tại, và nếu không rơi trở lại gói khác nếu nó tồn tại .

Điều này không trả về gì, vì lệnh đầu tiên trả về thành công (vì vậy rhs ||không bao giờ chạy)

apt-cache search hunspell-en-zz || apt-cache search hunspell-en

apt-cache search với hai đối số

Điều này không trả về gì cả, như apt-cachecác đối số của AND,

apt-cache search hunspell-en-zz hunspell-en

Từ các tài liệu của apt-cache

Các đối số riêng biệt có thể được sử dụng để chỉ định nhiều mẫu tìm kiếm được kết hợp với nhau.

Vì vậy, một trong những đối số rõ ràng không tồn tại, điều này không trả về gì cả.

Câu hỏi

Thành ngữ shell để xử lý các quy ước như những gì được tìm thấy trong apt-cacheđó mã trả về là vô dụng cho nhiệm vụ? Và thành công chỉ được xác định bởi sự hiện diện của đầu ra trên STDOUT?

Tương tự như

  • tìm thất bại khi không tìm thấy gì

    cả hai đều xuất phát từ cùng một vấn đề. Câu trả lời được chọn có đề cập đến find -zđiều đáng buồn là giải pháp không thể áp dụng ở đây và là trường hợp sử dụng cụ thể. Không có đề cập đến một thành ngữ hoặc xây dựng một đường ống mà không sử dụng kết thúc null (không phải là một tùy chọn trên apt-cache)


Bạn có chắc là hunspell-encó tồn tại? Dù sao, bạn có thể sử dụng apt-cache policyvà grep cho ^$PACKAGENAME:.
AlexP

@AlexP đây chỉ là những ví dụ hunspell-en không tồn tại vì chúng đóng gói với tên quốc gia, hunspell-arkhông tồn tại và không có gói tên quốc gia. Tôi cần tìm gói chính xác nhất cho một quốc gia và ngôn ngữ nhất định.
Evan Carroll

2
findcũng giống như apt-cacheở khía cạnh này - mã trả lại vô dụng, thành công dựa trên đầu ra.
muru

1
Vâng, tôi đồng ý rằng cả hai đều xuất phát từ cùng một vấn đề. Câu trả lời được chọn đề cập đến có đề cập -zrằng đáng buồn không phải là một giải pháp ở đây vì vậy vấn đề cụ thể theo trường hợp sử dụng không được áp dụng. Và không có đề cập đến một thành ngữ hoặc xây dựng một đường ống mà không sử dụng chấm dứt null (không phải là một tùy chọn trên apt-cache)
Evan Carroll

1
@EvanCarroll việc chấm dứt null là hoàn toàn tùy chọn. Tôi chỉ sử dụng nó bởi vì đó là cách an toàn nhất để xử lý tên tệp, vì vậy người ta sẽ mong đợi findđược sử dụng -print0và vì vậy grep với -z. Vì apt-cache không cung cấp đầu ra kết thúc null, nên bạn không cần -z.
muru

Câu trả lời:


5

Tạo một hàm nhận lệnh và trả về đúng nếu nó có một số đầu ra.

r() { local x=$("$@"); [ -n "$x" ] && echo "$x"; }

( ( r echo -n ) || echo 'nada' ) | cat      # Prints 'nada'
( ( r echo -n foo ) || echo 'nada' ) | cat  # Prints 'foo'

Vì vậy, đối với trường hợp sử dụng này, nó sẽ hoạt động như thế này,

r apt-cache search hunspell-en-zz || r apt-cache search hunspell-en

Lưu ý rằng r printf '\n\n\n'sẽ trả về false. Với vỏ khác zsh, r printf '\0\0\0'cũng sẽ trả về false. Vì vậy, r printf '\0a\0b\0c'với một số vỏ.
Stéphane Chazelas

3

Theo tôi biết, không có cách tiêu chuẩn nào để đối phó với những trường hợp mà sự thành công của một lệnh được xác định bởi sự hiện diện của đầu ra. Bạn có thể viết cách giải quyết, mặc dù.

Ví dụ: bạn có thể lưu đầu ra của lệnh trong một biến và sau đó kiểm tra xem biến đó có trống hay không:

output="$(command)"

if [[ -n "${output}" ]]; then
  # Code to execute if command succeded
else
  # Code to execute if command failed
fi

Tôi nghĩ rằng điều này trả lời câu hỏi một cách chung chung, nhưng nếu chúng ta nói về apt-cache searchmột số giải pháp đến với tâm trí của tôi.

Tôi có một kịch bản giúp quản lý gói dễ dàng hơn. Một số chức năng của nó là:

search() {
  local 'package' 'packages'
  packages="$( apt-cache search '.*' | cut -d ' ' -f '1' | sort )"
  for package; do
    grep -F -i -e "${package}" <<< "${packages}"
  done
}


search_all() {
  local 'package'
  for package; do
    apt-cache search "${package}" | sort
  done
}


search_description() {
  local 'package' 'packages'
  packages="$( apt-cache search '.*' | sort )"
  for package; do
    grep -F -i -e "${package}" <<< "${packages}"
  done
}


search_names_only() {
  local 'package'
  for package; do
    apt-cache search --names-only "${package}" | sort
  done
}

Chúng cho phép bạn thực hiện nhiều tìm kiếm trong một lệnh. Ví dụ:

$ search hunspell-en-zz hunspell-en
hunspell-en-au
hunspell-en-ca
hunspell-en-gb
hunspell-en-med
hunspell-en-us
hunspell-en-za

Mỗi chức năng tìm kiếm cơ sở dữ liệu theo một cách khác nhau, vì vậy kết quả có thể khác nhau tùy thuộc vào chức năng bạn sử dụng:

$ search gnome | wc -l
538
$ search_all gnome | wc -l
1322
$ search_description gnome | wc -l
822
$ search_names_only gnome | wc -l
550

2

Tôi sẽ không gọi đây là thanh lịch nhưng tôi nghĩ nó có thể thực hiện công việc:

search_packages () {
    local packages=($@)
    local results=()
    for package in "${packages[@]}"; do
        results=($(apt-cache -n search "$package"))
        if [[ "${#results[@]}" -eq 0 ]]; then
            echo "$package not found."
        elif [[ "${#results[@]}" -eq 1 ]]; then
            do stuff with "$package"
        else
            echo "Warning! Found multiple packages for ${package}:"
            printf '\t-> %s\n' "${results[@]}"
        fi
    done
}

Tôi không có một máy debian để kiểm tra không may. Tôi đã bao gồm -ntùy chọn "chỉ tên" apt-cacheđể thử và giới hạn kết quả tìm kiếm vì có vẻ như bạn chắc chắn về những gì bạn đang tìm kiếm.

Có thể chạy như sau:

$ search_packages hunspell-en-zz hunspell-en
$ my_packages=('hunspell-en-zz' 'hunspell-en')
$ search_packages "${my_packages[@]}"

1
Đây chính xác là những gì tôi đã nghĩ làm, tuy nhiên tôi đang tìm kiếm thứ gì đó thanh lịch hơn một chút, vì vậy hãy xem liệu có ai có bất cứ điều gì thông minh khác không (như một giải pháp trừu tượng hơn khỏi trường hợp sử dụng) nếu không tôi sẽ đánh dấu nó như đã chọn
Evan Carroll

1
Lý tưởng nhất, apt-cache sẽ trả lại một cái gì đó ít ngu ngốc hơn.
Evan Carroll

1
@EvanCarroll, Bạn đã thử nhắn tin với -qtùy chọn im lặng chưa? Trang man không quá dài dòng trên đó nhưng có lẽ nó thay đổi giá trị trả về?
jesse_b

1
vẫn trả về 0. = (
Evan Carroll

2

Muru đã làm rõ điều này trong các bình luận grepsẽ trả về trạng thái 1 nếu không có đầu vào. Vì vậy, bạn có thể thêm grep .vào luồng và nếu không có đầu vào nào khớp với mẫu ., nó sẽ thay đổi mã trạng thái:

( ( echo -n | grep . ) || echo 'nada' ) | cat      # prints 'nada'
( ( echo -n foo | grep . ) || echo 'nada' ) | cat  # prints 'foo'

Đối với trường hợp sử dụng trông như thế này. Ở bên dưới, không có gì -pl-plđể nó rơi lại và trở vềhunspell-pl

apt-cache search hunspell-pl-pl | grep . || apt-cache search hunspell-pl

Hoặc là,

apt-cache search hunspell-en-US | grep . || apt-cache search hunspell-en

Có một -en-USvì vậy nó trở lại hunspell-en-us.

Xem thêm,


grep .trả về true nếu đầu vào chứa ít nhất một dòng (được phân tách đầy đủ với một số triển khai) có chứa ít nhất một ký tự (được tạo tốt với hầu hết các cài đặt) và sẽ loại bỏ các dòng trống. grep '^'sẽ hoạt động tốt hơn khi kiểm tra xem có một số đầu ra, mặc dù với một số triển khai vẫn có thể trả về false nếu đầu vào là một dòng không phân cách (và có thể loại bỏ dòng đó, hoặc với các triển khai khác, trả về true nhưng thêm dòng mới bị thiếu). Một số triển khai grep cũng gây nghẹt thở cho nhân vật NUL.
Stéphane Chazelas

2

Bạn có thể định nghĩa một:

has_output() {
  LC_ALL=C awk '1;END{exit!NR}'
}

Và sau đó:

if cmd | has_output; then
  echo cmd did produce some output
fi

Một số awktriển khai có thể gây nghẹt cho các ký tự NUL trong đầu vào.

Trái với grep '^', ở trên sẽ được đảm bảo để làm việc trên một đầu vào không kết thúc bằng một ký tự dòng mới, nhưng sẽ thêm dòng mới bị thiếu.

Để tránh điều đó và có thể di động tới các hệ thống có awkcuộn cảm trên NUL, bạn có thể sử dụng perlthay thế:

has_output() {
  perl -pe '}{exit!$.'
}

Với perl, bạn cũng có thể xác định một biến thể xử lý các tệp tùy ý một cách duyên dáng hơn:

has_output() {
  PERLIO=:unix perl -pe 'BEGIN{$/=\65536} END{exit!$.}'
}

Điều đó giới hạn việc sử dụng bộ nhớ (như đối với các tệp không có ký tự dòng mới như các tệp thưa thớt lớn).

Bạn cũng có thể tạo các biến thể như:

has_at_least_one_non_empty_line() {
  LC_ALL=C awk '$0 != "" {n++};1; END{exit!n}'
}

hoặc là:

has_at_least_one_non_blank_line() {
  awk 'NF {n++};1; END{exit!n}'
}

(hãy cẩn thận định nghĩa của trống khác nhau giữa awkhiện thực, một số nơi nó giới hạn trong không gian và tab, một số nơi nó cũng bao gồm ký tự ASCII khoảng cách thẳng đứng như CR hoặc FF, một số nơi nó coi khoảng trống của locale)

Lý tưởng nhất là trên Linux, bạn muốn sử dụng lệnh splice()gọi hệ thống để tối đa hóa hiệu suất. Tôi không biết một lệnh đó sẽ phơi bày nó nhưng bạn luôn có thể sử dụng pythonctypes:

has_output() {
  python -c 'if 1:
    from ctypes import *
    import sys
    l = CDLL("libc.so.6")
    ret = 1
    while l.splice(0,0,1,0,65536,0) > 0:
      ret = 0
    sys.exit(ret)'
}

(lưu ý rằng has_outputstdin hoặc stdout (hoặc cả hai) phải là một đường ống splice()để làm việc).


0

Tôi sẽ đề nghị sử dụng các hàm dựng sẵn rất cơ bản của shell:

ck_command() { [ -n $("$@") ] ; }

Đây là trường hợp thử nghiệm đơn giản nhất:

ck_command echo 1 ; echo $?

ck_command echo ; echo $?

Sau đó, bạn có thể dễ dàng sử dụng nó với ||cấu trúc bạn đã quen:

ck_command command_1 || ck_command command_2

Hàm đơn giản này sẽ hoạt động như bạn muốn với apt_cachehành vi của bạn cho dù số lượng đối số sẽ là bao nhiêu.


Ngoại trừ điều này làm mất STDOUT trong quá trình, ck_command echo 'asdf' | catkhông có kết quả.
Evan Carroll

2
→ EvanCarroll: điều này không có trong "Câu hỏi" của bạn. Để đạt được sự bảo tồn đầu ra này, hãy xem câu trả lời rất thanh lịch và đơn giản từ @roaima: unix.stackexchange.com/a/413344/31707 .
dan
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.