Grep: hasil yang tidak terduga saat mencari kata-kata dalam pos dari halaman manual

19

Saya mengalami perilaku aneh ketika mencoba untuk membuka halaman manual di macOS. Misalnya, halaman manual Bash jelas memiliki kemunculan string NAME:

$ man bash | head -5 | tail -1
NAME

Dan jika saya menerima karena namesaya mendapatkan hasil, tetapi jika saya menerima karena NAMEsaya tidak:

$ man bash | grep 'NAME'
$ man bash | grep NAME

Saya sudah mencoba kata-kata huruf besar lain yang saya tahu ada di sana, dan mencari SHELLhasil apa-apa selain mencari BASHhasil.

Apa yang terjadi di sini?

Perbarui : Terima kasih atas semua jawaban! Saya pikir itu layak menambahkan konteks di mana saya mengalami ini. Saya ingin menulis fungsi bash untuk dibungkus mandan dalam kasus-kasus di mana saya mencoba mencari halaman manual untuk sebuah shell bawaan, lompat ke bagian yang relevan dari halaman manual Bash. Mungkin ada cara yang lebih baik, tapi inilah yang saya dapatkan saat ini:

man () {
  case "$(type -t "$1")" in
    builtin)
      local pattern="^ *$1"

      if bashdoc_match "$pattern \+[-[]"; then
        command man bash | less --pattern="$pattern +[-[]"
      elif bashdoc_match "$pattern\b"; then
        command man bash | less --pattern="$pattern[[:>:]]"
      else
        command man bash
      fi
      ;;
    keyword)
      command man bash | less --hilite-search --pattern='^SHELL GRAMMAR$'
      ;;
    *)
      command man "$@"
      ;;
  esac
}

bashdoc_match() {
  command man bash | col -b | grep -l "$1" > /dev/null
}
ivan
sumber
Sistem operasi apa yang Anda gunakan? Saya yakin jawaban yang diterima benar tetapi IO tidak dapat mereproduksi ini di kotak Linux Arch saya. man bash | grep NAMEbekerja seperti yang diharapkan.
terdon
@terdon saya menggunakan MacOS. Saya mendapatkan perilaku ini dengan Bash 3.2 dan 4.4.5
ivan
Sama seperti tambahan: jika Anda mendeteksi builtin, Anda bisa menggunakan helpperintah bash untuk mendapatkan informasinya.
Joe
@Joe Masalahnya adalah saya sering menemukan helphasilnya terlalu banyak. Lihat help completevs completebagian dalam man bash, misalnya.
ivan

Jawaban:

33

Jika Anda menambahkan | sed -n lke tailperintah itu, untuk menampilkan karakter yang tidak dapat dicetak, Anda mungkin akan melihat sesuatu seperti:

N\bNA\bAM\bME\bE

Artinya, setiap karakter ditulis sebagai XBackspace X. Pada terminal modern, karakter akhirnya ditulis sendiri (karena Backspace alias BS alias \balias ^Hadalah karakter yang menggerakkan kursor satu kolom ke kiri) tanpa perbedaan. Tetapi dalam mesin tik tele-kuno, itu akan menyebabkan karakter muncul dalam huruf tebal karena mendapat tinta dua kali lebih banyak.

Namun, pager suka more/ lessmengerti format itu artinya tebal, jadi itu yang membuat roffteks tebal.

Beberapa implementasi manusia akan memanggil roffdengan cara bahwa urutan itu tidak digunakan (atau panggilan internal col -b -p -xuntuk menghapusnya seperti dalam kasus man-dbimplementasi (kecuali MAN_KEEP_FORMATTINGvariabel lingkungan diatur)), dan tidak meminta pager ketika mereka mendeteksi output tidak pergi ke terminal (jadi man bash | grep NAMEakan bekerja di sana), tetapi bukan milik Anda.

Anda dapat menggunakan col -buntuk menghapus urutan tersebut (ada tipe lain ( _BS X) juga untuk garis bawah).

Untuk sistem yang menggunakan GNU roff(seperti GNU atau FreeBSD), Anda dapat menghindari urutan yang digunakan di tempat pertama dengan memastikan -c -b -uopsi diteruskan grotty, misalnya dengan memastikan -P-cbuopsi dilewatkan ke groff.

Misalnya dengan membuat skrip wrapper yang disebut groffmengandung:

#! /bin/sh -
exec /usr/bin/groff -P-cbu "$@"

Anda menempatkan / usr / bin / groff di depan $PATH.

Dengan macOS ' man(juga menggunakan GNU roff), Anda dapat membuat man-no-overstrike.confdengan:

NROFF /usr/bin/groff -mandoc -Tutf8 -P-cbu

Dan sebut mansebagai:

man -C man-no-overstrike.conf bash | grep NAME

Masih dengan GNU roff, jika Anda mengatur GROFF_SGRvariabel lingkungan (atau tidak mengatur GROFF_NO_SGRvariabel tergantung pada bagaimana default telah ditetapkan pada waktu kompilasi), maka grotty(selama itu tidak lulus -copsi) akan menggunakan urutan pelarian terminal ANSI SGR sebagai gantinya dari trik BS untuk atribut karakter. lessmemahaminya saat dipanggil dengan -Ropsi.

Orang FreeBSD memanggil grottydengan -copsi kecuali Anda meminta warna dengan mengatur variabel MANCOLOR (dalam hal -cini tidak diteruskan ke grottydan grottykembali ke default menggunakan ANSI SGR urutan melarikan diri di sana).

MANCOLOR=1 man bash | grep NAME

akan bekerja di sana.

Di Debian, GROFF_SGR bukan default. Jika kamu melakukan:

GROFF_SGR=1 man bash | grep NAME

Namun, karena manstdout bukan terminal, dibutuhkan sendiri untuk meneruskan GROFF_NO_SGRvariabel ke grotty(saya kira sehingga dapat digunakan col -bpxuntuk menghapus urutan BS karena coltidak tahu cara menghapus urutan SGR, meskipun masih melakukannya dengan MAN_KEEP_FORMATTING) yang menimpa GROFF_SGR. Anda bisa melakukannya:

GROFF_SGR=1 MANPAGER='grep NAME' man bash

(di terminal) untuk mendapatkan urutan pelarian SGR.

Saat itu, Anda akan melihat bahwa beberapa dari NAMA itu tampil dengan huruf tebal di terminal (dan dalam less -Rpager). Jika Anda memberi makan output ke sed -n l( MANPAGER='sed -n /NAME/l'), Anda akan melihat sesuatu seperti:

\033[1mNAME\033[0m$

Di mana \e[1murutan untuk mengaktifkan huruf tebal di terminal yang kompatibel dengan ANSI, dan \e[0murutan untuk mengembalikan semua atribut SGR ke default.

Pada teks itu grep NAMEberfungsi sebagai teks yang memang berisi NAME, tetapi Anda masih bisa memiliki masalah jika mencari teks di mana hanya bagian itu yang dicetak tebal / bergaris bawah ...

Stéphane Chazelas
sumber
2
Wow, cukup menarik melihat warisan tele-type fisik di sana. Tinta dua kali lebih banyak => tebal. Masuk akal
ivan
1
Saya mencintai sed -n lsebagai pengganti od.
Tom Hale
13

Jika Anda melihat halaman manual apa pun, Anda akan melihat bahwa judulnya dicetak tebal. Ini dicapai melalui pemformatan dengan karakter kontrol. Untuk dapat grepmenyukai yang Anda inginkan, ini harus dihilangkan.

The colutilitas dapat digunakan untuk ini:

$ man bash | col -b | grep 'NAME'

The -bpilihan memiliki deskripsi berikut pada OpenBSD :

Jangan mengeluarkan spasi mundur, hanya mencetak karakter terakhir yang ditulis untuk setiap posisi kolom. Ini dapat berguna dalam memproses output dari mandoc (1).


Linux colmanual (di Ubuntu) tidak memiliki kalimat terakhir di sana (tetapi bekerja dengan cara yang sama).

Di Linux, membatalkan pengaturan MAN_KEEP_FORMATTINGvariabel lingkungan (atau menyetelnya ke string kosong) juga dapat membantu, dan akan memungkinkan Anda untuk greptanpa melewati keluaran manthrough col -b.

Kusalananda
sumber
Saya pikir (seperti pada saya menguji ini pada Arch dan sistem Ubuntu) bahwa di Linux ini tidak perlu, atau tidak lagi. Pada kedua sistem, manual NAMEdi bash adalah adil NAME, tidak \b.
terdon
@terdon Saya tidak menemukan penyebutan macOS terlebih dahulu, jadi saya berasumsi bahwa sistem Linux yang salah dikonfigurasi adalah suatu kemungkinan. Saya sekarang telah memangkas bit Linux.
Kusalananda
Anda tidak melewatkan apa pun, saya bertanya kepada OP OS apa yang mereka gunakan karena saya tidak dapat mereproduksi di Linux, mereka mengatakan macOS dan saya baru saja menambahkannya sekarang. Dan saya tidak menyiratkan Anda salah, karena yang saya tahu ada distribusi Linux di luar sana di mana MAN_KEEP_FORMATTINGvariabel bekerja persis seperti yang Anda katakan. Saya hanya ingin menunjukkan bahwa tidak selalu demikian.
terdon