Gunakan sed untuk mengganti alamat IP dengan nama host di output log

2

Saya mencoba untuk mengganti alamat IP dalam dnsmasq logfile dengan nama hostnya. File log sedang 'diawasi' dengan perintah 'tail -f /var/log/dnsmasq.log' di konsol dan saya ingin menyalurkan output ke sed untuk mengganti alamat IP dengan nama host pada HANYA baris yang berisi teks 'permintaan'. Alamat IP selalu di akhir baris ini.

Baris contoh adalah:

Apr  1 00:47:43 dnsmasq[1004]: query[A] gs-loc.apple.com from 10.1.1.188

Saya percaya perintahnya akan dalam bentuk:

tail -f /var/log/dnsmasq.log | sed -e "s/'regex'/$(dig +short -x $1)/g"

'Regex' perlu mengidentifikasi baris yang berisi string "query", mengekstrak alamat IP dari akhir baris itu dan menyimpannya (entah bagaimana) dalam variabel - saya menggunakan notasi di $1sini - yang digunakan dalam ekspresi ganti dengan menggali.

UPDATE: Saya dihilangkan untuk menyebutkan bahwa alamat IP akan selalu dalam bentuk 10.1.nn

deanodley
sumber
2
Apakah harus sed? Alat lain (seperti awk atau perl) mungkin memiliki solusi yang lebih mudah dibaca.
Jeff Schaller
coba ini: sed 's / [^] * $ / REPLACEMENT /'
Lety
Ya ini berfungsi tetapi saya hanya perlu menargetkan baris yang memiliki string "query" di baris sebelumnya.
deanodley
@JeffSchaller - tidak tidak harus sed, bahkan bisa berupa file skrip, apa pun yang berfungsi! Terima kasih
deanodley
Apakah alamat IP selalu terakhir di telepon?
Kusalananda

Jawaban:

1

Sayangnya sed tidak dapat menjalankan perintah eksternal sementara juga melewati parameter yang diambil dari inputnya.

Ini adalah solusi skrip Bash yang harus Anda lakukan:

tail -f dnsmasq.log | { while IFS= read -r line ; do { [[ "${line}" =~ ": query[A]" ]] && printf '%s %s\n' "${line% *} " $(dig +short -x "${line##* }"); } || echo "${line}"; done ; }

Dipecah untuk penjelasan : ( hanya untuk tujuan kejelasan, mungkin tidak berfungsi saat disalin & ditempelkan )

tail -f dnsmasq.log | \
    { \
        while IFS= read -r line ; do \           # for each line read in from tail ...
            if [[ "${line}" =~ ": query[A]" ]] ; # if it has the literal string ': query[A]'
            then \
                printf '%s %s\n' "${line% *} " \ # print it (purged of last field, which is the IP address) ...
                $(dig +short -x "${line##* }") \ # along with dig's output
            else \                               # otherwise ...
                echo "${line}" \                 # just print it all as it is
            fi \
        done ; \
    }
LL3
sumber
Wah ini sangat pintar! Saya tidak sadar dan tidak bisa meneruskan args ke perintah jadi saya mengejar ekor saya, tetapi skrip Anda akan menyelamatkan hari. Terima kasih banyak!
deanodley
Sebenarnya hanya memperhatikan bahwa skrip hanya memilih baris dengan "query" secara efektif membuang semua baris lainnya. Triknya adalah bahwa saya perlu jalur lain ini untuk melewati :(
deanodley
@deododley Oh benar! Saya pikir kamu menginginkan itu! tidak masalah, koreksi selesai!
LL3
1

Ini agak, agak berfungsi (tetapi menggunakan 'awk' bukan 'sed'):

$ echo $'Apr  1 00:47:43 dnsmasq[1004]: query[A] gs-loc.apple.com from 8.8.8.8' | awk '/query/{ IP=$NF; $NF=""; L=$0; "host " IP | getline name; $0=name; print L,$NF }'
Apr 1 00:47:43 dnsmasq[1004]: query[A] gs-loc.apple.com from  google-public-dns-a.google.com.

... membutuhkan sedikit pemoles misalnya jika pencarian host gagal; mungkin 'permintaan' regex harus sedikit lebih spesifik.

Berikut ini penjelasan tentang perintah awk:

/ query / {...} jalankan {...} pada baris yang cocok dengan regex 'query' (cukup cetak yang lain)

IP = $ NF set variabel baru 'IP' ke nilai bidang terakhir pada baris (alamat IP)

$ NF = "" zap bidang terakhir pada baris

L = $ 0 atur variabel baru 'L' ke baris yang tersisa (yaitu tanpa alamat IP)

"host" IP | getline name jalankan 'host' pada alamat IP dan masukkan hasilnya dalam 'nama' variabel baru

$ 0 = nama atur baris saat ini ke output dari perintah 'host' sehingga kita dapat menggunakan $ NF pada perintah berikutnya.

print L, $ NF Print 'L' (baris input tanpa alamat IP) dan bidang terakhir dari perintah 'host' (nama host).

wef
sumber
Maaf tidak bisa mengikuti ini dan tidak menghasilkan output untuk saya.
deanodley
Saya menambahkan contoh pengujian saya - Saya tidak bisa mendapatkan 10.1.1.188 untuk diselesaikan sehingga saya menggunakan 8.8.8.8. Jika saya punya waktu hari ini, saya akan mencoba memecah perintah awk dan menjelaskannya.
wef
Penjelasan perintah 'awk' ditambahkan.
wef
0

Berjalan diguntuk setiap alamat IP akan sangat tidak efisien dan menambah beban ke server DNS Anda. Saya akan gunakan di perlsini:

perl -MSocket -pe 's{(?<![\d.])\d+\.\d+\.\d+\.\d+(?![\d.])}{
    $ip = inet_aton($&);
    $cache{$ip} //= gethostbyaddr($ip,AF_INET) // "UNKNOWN[$&]"
  }ge'

Itu menanyakan layanan nama sistem Anda, jadi mungkin /etc/hosts, DNS, mDNS, LDAP, NIS + ... atau apa pun yang dikonfigurasi untuk resolusi nama host di /etc/nsswitch.confatau setara pada sistem Anda, mungkin melalui layanan caching layanan nama seperti nscdatau sssd, dan kami mengimplementasikan caching kami juga untuk menghindari permintaan alamat IP yang sama beberapa kali.

Kami hanya mencocokkan untuk urutan .angka desimal terpisah -4 , bukan format alamat IPv4 lainnya, tetapi perhatikan bahwa untuk inet_aton(), memimpin 0s menyebabkan angka dianggap sebagai oktal, jadi 010.010.010.010sebenarnya 8.8.8.8(sama seperti kebanyakan hal yang menggunakan alamat IP sebagai argumen, tapi tidak dig -x).

Jika Anda membutuhkannya untuk menanyakan server DNS seperti yang digdilakukan, Anda dapat menggunakannya Net::DNSsebagai ganti gethostbyaddr():

perl -MNet::DNS -pe '
  sub resolve {
    my ($r) = rr($_[0]);
    if (defined($r)) {
      return $r->ptrdname;
    } else {
      return "UNKNOWN[$_[0]]";
    }
  }
  s{(?<![\d.])\d+\.\d+\.\d+\.\d+(?![\d.])}{$cache{$&} //= resolve $&}ge'
Stéphane Chazelas
sumber