Cara elegan untuk membangun pipa berdasarkan nilai balik dan bukan kode keluar?

8

Ketika kode status tidak berguna, apakah ada cara untuk membangun pipa berdasarkan output dari stdout?

Saya lebih suka jawabannya tidak membahas use-case tetapi pertanyaan dalam lingkup scripting shell. Apa yang saya coba lakukan adalah menemukan paket paling spesifik yang tersedia di repositori dengan menebak nama berdasarkan kode negara dan bahasa.

Ambil contoh ini,

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

Tebakan pertama lebih tepat tetapi mungkin tidak ada. Dalam hal ini, saya ingin kembali hunspell-en( $PACKAGE2) karena opsi pertama hunspell-en-zz( $PACKAGE1) tidak ada.

jalur pipa apt-cache

Perintah apt-cachemengembalikan kesuksesan (yang didefinisikan oleh shell sebagai kode keluar nol) setiap kali perintah dapat dijalankan (dari dokumen apt-cache)

apt-cache mengembalikan nol pada operasi normal, 100 desimal pada kesalahan.

Itu membuat menggunakan perintah dalam pipa lebih sulit. Biasanya, saya mengharapkan pencarian paket setara dengan 404 untuk menghasilkan kesalahan (seperti yang akan terjadi dengan curlatau wget). Saya ingin mencari untuk melihat apakah ada paket, dan jika tidak kembali ke paket lain jika ada .

Ini tidak menghasilkan apa-apa, karena perintah pertama mengembalikan kesuksesan (jadi rhs dalam yang ||tidak pernah berjalan)

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

apt-cache search dengan dua argumen

Ini tidak menghasilkan apa-apa, seperti apt-cacheargumen ANDs,

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

Dari dokumen apt-cache

Argumen terpisah dapat digunakan untuk menentukan beberapa pola pencarian yang and'ed bersama.

Jadi karena salah satu argumen itu jelas tidak ada, ini tidak menghasilkan apa-apa.

Pertanyaan

Apa idiom shell untuk menangani konvensi seperti yang ditemukan di apt-cachemana kode kembali tidak berguna untuk tugas itu? Dan kesuksesan hanya ditentukan oleh kehadiran output pada STDOUT?

Mirip dengan

  • membuat menemukan gagal ketika tidak ada yang ditemukan

    mereka berdua berasal dari masalah yang sama. Jawaban yang dipilih di sana menyebutkan find -zsolusi mana yang sayangnya tidak dapat diterapkan di sini dan khusus untuk kasus penggunaan. Tidak ada penyebutan idiom atau membangun pipa tanpa menggunakan null-termination (bukan opsi aktif apt-cache)

Evan Carroll
sumber
Apakah Anda yakin itu hunspell-enada? Bagaimanapun, Anda dapat menggunakan apt-cache policydan menerima ^$PACKAGENAME:.
AlexP
@AlexP ini hanya contoh hunspell-en tidak ada karena mereka mengemas dengan nama negara, hunspell-armemang ada dan tidak ada paket nama negara. Saya perlu menemukan paket paling akurat untuk negara dan bahasa tertentu.
Evan Carroll
2
findsama seperti apt-cachedalam hal ini - kode pengembalian tidak berguna, kesuksesan didasarkan pada output.
muru
1
Ya, saya setuju mereka berdua berasal dari masalah yang sama. Jawaban yang dipilih menyebutkan di sana menyebutkan -zyang sayangnya bukan solusi di sini sehingga masalah penggunaan-spesifik kasus tidak berlaku. Dan tidak ada penyebutan idiom atau membangun saluran pipa tanpa menggunakan null-termination (bukan opsi pada apt-cache)
Evan Carroll
1
@EvanCarroll, terminasi nol sepenuhnya opsional. Saya hanya menggunakannya karena ini adalah cara teraman untuk berurusan dengan nama file, jadi orang akan berharap finduntuk digunakan -print0dan jadi mengerti -z. Karena apt-cache tidak memberikan keluaran yang diakhiri null, Anda tidak perlu -z.
muru

Jawaban:

5

Buat fungsi yang mengambil perintah dan mengembalikan true jika memiliki beberapa output.

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

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

Jadi untuk kasus penggunaan ini akan berfungsi seperti ini,

r apt-cache search hunspell-en-zz || r apt-cache search hunspell-en
roaima
sumber
Perhatikan bahwa r printf '\n\n\n'akan kembali salah. Dengan kerang selain zsh, r printf '\0\0\0'juga akan kembali palsu. Begitu juga r printf '\0a\0b\0c'dengan beberapa kerang.
Stéphane Chazelas
3

Sejauh yang saya tahu, tidak ada cara standar untuk menangani kasus-kasus di mana keberhasilan suatu perintah ditentukan oleh kehadiran output. Anda bisa menulis solusinya.

Misalnya, Anda dapat menyimpan output dari perintah dalam variabel dan kemudian memeriksa apakah variabel itu kosong atau tidak:

output="$(command)"

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

Saya pikir ini menjawab pertanyaan secara umum, tetapi jika kita berbicara tentang apt-cache searchbeberapa solusi datang ke pikiran saya.

Saya memiliki skrip yang membuat manajemen paket lebih mudah. Beberapa fungsinya adalah sebagai berikut:

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
}

Ini memungkinkan Anda untuk melakukan beberapa pencarian dalam satu perintah. Sebagai contoh:

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

Setiap fungsi mencari database dengan cara yang berbeda, sehingga hasilnya dapat bervariasi tergantung pada fungsi yang Anda gunakan:

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

Saya tidak akan menyebut ini anggun, tetapi saya pikir ini mungkin berhasil:

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
}

Sayangnya saya tidak memiliki mesin debian untuk diuji. Saya telah menyertakan opsi -nuntuk "hanya nama" apt-cacheuntuk mencoba dan membatasi hasil pencarian karena sepertinya Anda sebagian besar yakin dengan apa yang Anda cari.

Dapat dijalankan seperti:

$ search_packages hunspell-en-zz hunspell-en
$ my_packages=('hunspell-en-zz' 'hunspell-en')
$ search_packages "${my_packages[@]}"
jesse_b
sumber
1
Ini adalah persis apa yang saya pikirkan untuk dilakukan, namun saya mencari sesuatu yang sedikit lebih elegan, jadi mari kita lihat apakah ada orang lain yang pintar (seperti solusi yang lebih abstrak dari use-case) jika tidak saya akan menandai seperti yang dipilih.
Evan Carroll
1
Idealnya, apt-cache hanya akan mengembalikan sesuatu yang tidak sebodoh itu.
Evan Carroll
1
@ EvanCarroll, Sudahkah Anda mencoba mengotak-atik -qopsi yang sepi? Halaman manual tidak terlalu bertele-tele tentang itu tetapi mungkin itu mengubah nilai kembali?
jesse_b
1
masih mengembalikan 0. = (
Evan Carroll
2

Muru menjelaskan ini dalam komentar grepakan mengembalikan status 1 jika tidak ada input. Jadi Anda dapat menambahkan grep .ke aliran dan jika tidak ada input yang cocok dengan pola ., itu akan mengubah kode status:

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

Untuk kasus penggunaan yang terlihat seperti ini. Di bawah ini, tidak ada -pl-plsehingga jatuh kembali dan kembalihunspell-pl

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

Atau,

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

Ada -en-USsehingga kembali hunspell-en-us.

Lihat juga,

Evan Carroll
sumber
grep .mengembalikan true jika input mengandung setidaknya satu (dibatasi sepenuhnya dengan beberapa implementasi) baris yang berisi setidaknya satu (terbentuk dengan sebagian besar implementasi) karakter dan jika tidak akan menghapus baris kosong. grep '^'akan bekerja lebih baik untuk memeriksa bahwa ada beberapa output, meskipun dengan beberapa implementasi masih bisa mengembalikan false jika input adalah satu baris yang tidak dibatasi (dan dapat menghapus baris itu, atau dengan implementasi lain, mengembalikan nilai true tetapi menambahkan baris baru yang hilang). Beberapa implementasi grep juga mencekik karakter NUL.
Stéphane Chazelas
2

Anda dapat mendefinisikan:

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

Lalu:

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

Beberapa awkimplementasi mungkin tersedak karakter NUL dalam input.

Bertentangan dengan itu grep '^', di atas akan dijamin bekerja pada input yang tidak berakhir pada karakter baris baru, tetapi akan menambahkan baris baru yang hilang.

Untuk menghindari itu dan menjadi portabel untuk sistem di mana awktersedak NUL, Anda bisa menggunakan perl:

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

Dengan perl, Anda juga bisa menentukan varian yang menangani file sewenang-wenang dengan lebih anggun:

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

Itu membatasi penggunaan memori (seperti untuk file yang tidak memiliki karakter baris baru seperti file jarang besar).

Anda juga dapat membuat varian seperti:

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

atau:

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

(berhati-hatilah definisi blank bervariasi antara awkimplementasi, beberapa di mana terbatas pada ruang dan tab, beberapa di mana juga termasuk karakter spasi vertikal ASCII seperti CR atau FF, beberapa di mana ia menganggap kosong lokal)

Idealnya, di Linux, Anda ingin menggunakan splice()panggilan sistem untuk memaksimalkan kinerja. Saya tidak tahu dari perintah yang akan memaparkannya tetapi Anda selalu bisa menggunakan python's ctypes:

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)'
}

(perhatikan bahwa baik has_outputstdin atau stdout (atau keduanya) harus menjadi pipa untuk splice()bekerja).

Stéphane Chazelas
sumber
0

Saya akan menyarankan untuk menggunakan fungsi builtin yang sangat dasar dari shell:

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

Ini adalah contoh uji paling sederhana:

ck_command echo 1 ; echo $?

ck_command echo ; echo $?

Maka Anda dapat dengan mudah menggunakannya dengan ||konstruk yang biasa Anda gunakan:

ck_command command_1 || ck_command command_2

Fungsi sederhana ini akan berfungsi seperti yang Anda inginkan dengan apt_cacheperilaku Anda , berapa pun jumlah argumennya.

dan
sumber
Kecuali ini kehilangan STDOUT dalam proses, ck_command echo 'asdf' | cattidak menghasilkan apa-apa.
Evan Carroll
2
→ EvanCarroll: ini tidak ada dalam § "Pertanyaan Anda". Untuk juga mencapai konservasi keluaran ini, lihat jawaban yang sangat elegan dan sederhana dari @roaima: unix.stackexchange.com/a/413344/31707 .
dan