Bash: Ekstrak salah satu dari empat bagian dari alamat IPv4

9

Kita dapat menggunakan sintaks ${var##pattern}dan ${var%%pattern}untuk mengekstrak bagian terakhir dan pertama dari alamat IPv4:

IP=109.96.77.15
echo IP: $IP
echo 'Extract the first section using ${var%%pattern}: ' ${IP%%.*}
echo 'Extract the last section using ${var##pattern}: ' ${IP##*.}

Bagaimana kita dapat mengekstrak bagian kedua atau ketiga dari alamat IPv4 menggunakan ekspansi parameter?

Ini solusi saya: Saya menggunakan array dan mengubah variabel IFS.

:~/bin$ IP=109.96.77.15
:~/bin$ IFS=. read -a ArrIP<<<"$IP"
:~/bin$ echo ${ArrIP[1]}
    96
:~/bin$ printf "%s\n" "${ArrIP[@]}"
    109
    96
    77
    15

Juga saya telah menulis beberapa solusi menggunakan awk, seddan cutperintah.

Sekarang, pertanyaan saya adalah: Apakah ada solusi yang lebih sederhana berdasarkan ekspansi parameter yang tidak menggunakan perubahan array dan IFS?

sci9
sumber
1
Anda hanya harus mengatur IFSuntuk di readsana:IFS=. read -a ArrIP<<<"$IP"
muru
1
Paling tidak dalam bash tanpa menggunakan beberapa variabel. Perluasan parameter tunggal tidak bisa mendapatkan komponen kedua atau ketiga. Zsh dapat membuat parameter ekspansi, jadi mungkin saja ada.
muru
@muru Bisakah Anda memberikan solusi Zsh?
sci9
4
Apa jaminan yang Anda miliki bahwa Anda akan selalu berurusan dengan alamat IP v4, dan tidak akan pernah memiliki alamat IP v6?
Mawg mengatakan mengembalikan Monica
4
Apakah ada beberapa alasan IFS=. read a b c d <<< "$IP"yang tidak dapat diterima (jika Anda menggunakan Bash, itu)? Mengapa harus dilakukan dengan ekspansi parameter?
ilkkachu

Jawaban:

16

Dengan asumsi nilai default IFS Anda mengekstrak setiap oktet ke dalam variabel itu sendiri dengan:

read A B C D <<<"${IP//./ }"

Atau ke dalam array dengan:

A=(${IP//./ })
Jason Musgrove
sumber
2
+1. Tampak bagi saya bahwa ini adalah metode paling sederhana, paling mudah yang mematuhi pembatasan OPs.
Trauma Digital
7

Pernyataan masalah Anda mungkin sedikit lebih liberal daripada yang Anda maksudkan. Dengan risiko mengeksploitasi celah, inilah solusi yang disinggung oleh muru :

first=${IP%%.*}
last3=${IP#*.}
second=${last3%%.*}
last2=${last3#*.}
third=${last2%.*}
fourth=${last2#*.}
echo "$IP -> $first, $second, $third, $fourth"

Ini agak kikuk. Ini mendefinisikan dua variabel yang dibuang, dan tidak mudah disesuaikan untuk menangani lebih banyak bagian (misalnya, untuk alamat MAC atau IPv6).  Jawaban Sergiy Kolodyazhnyy menginspirasi saya untuk menggeneralisasi hal di atas sebagai berikut:

slice="$IP"
count=1
while [ "$count" -le 4 ]
do
    declare sec"$count"="${slice%%.*}"
    slice="${slice#*.}"
    count=$((count+1))
done

Set ini sec1, sec2, sec3dan sec4, yang dapat diverifikasi dengan

printf 'Section 1: %s\n' "$sec1"
printf 'Section 2: %s\n' "$sec2"
printf 'Section 3: %s\n' "$sec3"
printf 'Section 4: %s\n' "$sec4"
  • The whileloop harus mudah dipahami - iterates empat kali.
  • Sergiy memilih slicesebagai nama untuk variabel yang menggantikan last3dan last2dalam solusi pertama saya (di atas).
  • declare sec"$count"="value"adalah cara untuk menetapkan ke sec1, sec2, sec3dan sec4 saat countini 1, 2, 3dan 4. Agak mirip eval, tapi lebih aman.
  • The value,, "${slice%%.*}"analog dengan nilai yang diberikan oleh jawaban asli saya first, seconddan third.
G-Man Mengatakan 'Reinstate Monica'
sumber
6

Saya menyadari bahwa Anda secara khusus meminta solusi yang TIDAK DITENTUKAN sementara IFS, tetapi saya punya solusi manis dan sederhana yang tidak Anda bahas, jadi begini:

IFS=. ; set -- $IP

Bahwa perintah singkat akan menempatkan unsur-unsur alamat IP Anda di shell parameter posisi $1 , $2, $3, $4. Namun, Anda mungkin ingin menyimpan yang asli IFSdan mengembalikannya setelah itu.

Siapa tahu? Mungkin Anda akan mempertimbangkan kembali dan menerima jawaban ini karena singkat dan efisiensinya.

(Ini sebelumnya salah diberikan sebagai IFS=. set -- $IP)

pengguna1404316
sumber
5
Saya tidak berpikir itu bekerja: jika Anda berubah IFSpada baris perintah yang sama, nilai baru tidak berpengaruh ketika variabel pada baris perintah yang sama diperluas. Sama seperti denganx=1; x=2 echo $x
ilkkachu
6
@ chepner, tapi sekali lagi, settidak menggunakan $IFSsama sekali, $IFShanya digunakan untuk pemisahan kata $IP, tapi di sini sudah terlambat, sehingga tidak ada efeknya. Jawaban ini pada dasarnya salah. IP=109.96.77.15 bash -c 'IFS=. set -- $IP; echo "$2"'tidak menghasilkan apa-apa dalam mode POSIX atau tidak. Anda perlu IFS=. command eval 'set -- $IP', atauIFS=. read a b c d << "$IP"
Stéphane Chazelas
4
Mungkin berhasil untuk Anda karena Anda telah menetapkan IFS .di salah satu tes sebelumnya. Jalankan IP=1.2.3.4 bash -xc 'IFS=. set $IP; echo "$2"'dan Anda akan melihatnya tidak bekerja. Dan lihat IP=1.2.3.4 bash -o posix -xc 'IFS=. set $IP; echo "\$1=$1 \$2=$2 IFS=$IFS"'untuk mengilustrasikan poin @ chepner.
Stéphane Chazelas
2
Tergesa-gesa memang membuat limbah, jawabannya ternyata salah, dan harus diperbaiki untuk bekerja, karena masih kira-kira bagaimana saya akan melakukannya, hanya dengan lebih banyak kode.
Lizardx
3
@ user1404316, dapatkah Anda mengirim serangkaian perintah yang tepat yang Anda gunakan untuk mengujinya? (Plus versi shell Anda, mungkin.) Ada empat pengguna lain yang telah memberi tahu Anda di sini di komentar bahwa itu tidak berfungsi seperti yang tertulis dalam jawaban. Dengan contoh.
ilkkachu
5

Bukan yang termudah , tetapi Anda dapat melakukan sesuatu seperti:

$ IP=109.96.77.15
$ echo "$((${-+"(${IP//./"+256*("}))))"}&255))"
109
$ echo "$((${-+"(${IP//./"+256*("}))))"}>>8&255))"
96
$ echo "$((${-+"(${IP//./"+256*("}))))"}>>16&255))"
77
$ echo "$((${-+"(${IP//./"+256*("}))))"}>>24&255))"
15

Yang harus bekerja di ksh93 (mana yang ${var//pattern/replacement}Operator berasal dari), bash4.3+, busybox sh, yash, mkshdan zsh, meskipun tentu saja di zsh, ada pendekatan lebih sederhana . Di versi yang lebih lama bash, Anda harus menghapus kutipan dalam. Ini bekerja dengan kutipan batin yang dihapus di sebagian besar kerang lain juga, tetapi tidak ksh93.

Asumsi itu $IPberisi representasi quad-desimal valid dari alamat IPv4 (meskipun itu juga akan berfungsi untuk representasi quad-hexadecimal seperti 0x6d.0x60.0x4d.0xf(dan bahkan oktal dalam beberapa shell) tetapi akan menampilkan nilai dalam desimal). Jika konten $IPberasal dari sumber yang tidak dipercaya, itu akan berarti kerentanan injeksi perintah.

Pada dasarnya, seperti yang kita mengganti setiap .di $IPdengan +256*(, kita akhirnya mengevaluasi:

 $(( (109+256*(96+256*(77+256*(15))))>> x &255 ))

Jadi kita membangun integer 32 bit dari 4 byte seperti alamat IPv4 pada akhirnya adalah (meskipun dengan byte terbalik)) dan kemudian menggunakan >>, &operator bitwise untuk mengekstrak byte yang relevan.

Kami menggunakan ${param+value}operator standar (di sini $-yang dijamin akan selalu ditetapkan) alih-alih hanya valuekarena parser aritmatika akan mengeluh tentang kurung yang tidak cocok. Shell di sini dapat menemukan penutup ))untuk pembukaan $((, dan kemudian melakukan ekspansi di dalam yang akan menghasilkan ekspresi aritmatika untuk dievaluasi.

Dengan $(((${IP//./"+256*("}))))&255))gantinya, shell akan memperlakukan kedua dan ketiga )s ada sebagai penutup ))untuk $((dan melaporkan kesalahan sintaks.

Di ksh93, Anda juga dapat melakukan:

$ echo "${IP/@(*).@(*).@(*).@(*)/\2}"
96

bash, mksh, zsh Telah menyalin ksh93 ini ${var/pattern/replacement}Operator tapi tidak yang menangkap-kelompok penanganan bagian. zshmendukungnya dengan sintaks yang berbeda:

$ setopt extendedglob # for (#b)
$ echo ${IP/(#b)(*).(*).(*).(*)/$match[2]}'
96

bashmendukung beberapa bentuk penanganan grup tangkap di operator pencocokan regexp , tetapi tidak dalam ${var/pattern/replacement}.

POSIXly, Anda akan menggunakan:

(IFS=.; set -o noglob; set -- $IP; printf '%s\n' "$2")

Untuk noglobmenghindari kejutan buruk untuk nilai $IPsuka 10.*.*.*, subkulit untuk membatasi ruang lingkup perubahan tersebut ke opsi dan $IFS.


¹ Alamat IPv4 hanya integer 32 bit dan 127.0.0.1 misalnya hanyalah salah satu dari banyak (meskipun yang paling umum) representasi tekstual. Alamat IPv4 tipikal yang sama dari antarmuka loopback juga dapat direpresentasikan sebagai 0x7f000001 atau 127.1 (mungkin yang lebih tepat di sini adalah 1alamat pada jaringan 127.0 / 8 kelas A), atau 0177.0.1, atau kombinasi lain dari 1 ke 4 angka yang dinyatakan sebagai oktal, desimal atau heksadesimal. Anda dapat mengirimkan semua itu pingmisalnya dan Anda akan melihat mereka semua melakukan ping localhost.

Jika Anda tidak keberatan efek samping menetapkan variabel sementara sewenang-wenang (di sini $n), di bashatau ksh93atau zsh -o octalzeroesatau lksh -o posix, Anda dapat mengkonversi semua representasi mereka kembali ke integer 32 bit dengan:

$((n=32,(${IP//./"<<(n-=8))+("})))

Lalu ekstrak semua komponen dengan >>/ &kombinasi seperti di atas.

$ IP=0x7f000001
$ echo "$((n=32,(${IP//./"<<(n-=8))+("})))"
2130706433
$ IP=127.1
$ echo "$((n=32,(${IP//./"<<(n-=8))+("})))"
2130706433
$ echo "$((n=32,((${IP//./"<<(n-=8))+("}))>>24&255))"
127
$ perl -MSocket -le 'print unpack("L>", inet_aton("127.0.0.1"))'
2130706433

mkshmenggunakan bilangan bulat 32 bit yang ditandatangani untuk ekspresi aritmatika, Anda dapat menggunakannya di $((# n=32,...))sana untuk memaksa penggunaan angka 32 bit yang tidak ditandatangani (dan posixopsi untuknya mengenali konstanta oktal).

Stéphane Chazelas
sumber
Saya mengerti konsep besar, tetapi saya belum pernah melihat ${-+sebelumnya. Saya juga tidak dapat menemukan dokumentasi di situ. Berhasil, tapi saya hanya ingin memastikan, apakah hanya untuk mengubah string menjadi ekspresi matematika? Di mana saya dapat menemukan definisi formal? Juga, kutipan tambahan di dalam bagian penggantian ekspansi parameter tidak berfungsi di GNU bash, versi 4.1.2 (2) - rilis CentOS 6.6. Saya harus melakukan ini sebagai gantinyaecho "$((${-+"(${IP//./+256*(}))))"}>>16&255))"
Levi Uzodike
1
@LeviUzodike, lihat edit.
Stéphane Chazelas
4

Dengan zsh, Anda dapat membuat pengganti parameter:

$ ip=12.34.56.78
$ echo ${${ip%.*}##*.}
56
$ echo ${${ip#*.}%%.*}
34

Ini tidak mungkin dalam bash.

muru
sumber
1
Di zsh, Anda dapat memilih${${(s(.))ip}[3]}
Stéphane Chazelas
4

Tentu, mari kita mainkan permainan gajah.

$ ipsplit() { local IFS=.; ip=(.$*); }
$ ipsplit 10.1.2.3
$ echo ${ip[1]}
10

atau

$ ipsplit() { local IFS=.; echo $*; }
$ set -- `ipsplit 10.1.2.3`
$ echo $1
10
jthill
sumber
2
Apa itu "permainan gajah"?
Wildcard
1
@Wildcard semacam elips pun / lelucon, ini adalah referensi baik untuk orang-orang buta-menggambarkan kisah gajah, ada begitu banyak bagian untuk melihat semua orang akan memiliki pendapat sendiri, dan dengan kebiasaan kuno seorang raja yang ingin untuk memberikan hadiah dengan biaya pemeliharaan yang sangat mahal, melunak selama berabad-abad menjadi hadiah dengan nilai yang meragukan yang cenderung disesuaikan dengan nilai hiburan .. Keduanya tampaknya berlaku di sini :-)
jthill
3

Dengan IP=12.34.56.78.

IFS=. read a b c d <<<"$IP"

Dan

#!/bin/bash
IP=$1

regex="(${IP//\./)\.(})"
[[ $IP =~ $regex ]]
echo "${BASH_REMATCH[@]:1}"

Deskripsi:

Gunakan ekspansi parameter ${IP// }untuk mengonversi setiap titik di ip ke tanda kurung buka tanda kurung dan tanda kurung penutup. Menambahkan tanda kurung awal dan tanda kurung penutup, kita mendapatkan:

regex=(12)\.(34)\.(56)\.(78)

yang akan membuat empat kurung tangkap untuk kecocokan regex dalam sintaks tes:

[[ $IP =~ $regex ]]

Itu memungkinkan pencetakan array BASH_REMATCH tanpa komponen pertama (kecocokan seluruh regex):

echo "${BASH_REMATCH[@]:1}"

Jumlah tanda kurung secara otomatis disesuaikan dengan string yang cocok. Jadi, ini akan cocok dengan MAC atau EUI-64 dari alamat IPv6 meskipun panjangnya berbeda:

#!/bin/bash
IP=$1

regex="(${IP//:/):(})"
[[ $IP =~ $regex ]]
echo "${BASH_REMATCH[@]:1}"

Menggunakannya:

$ ./script 00:0C:29:0C:47:D5
00 0C 29 0C 47 D5

$ ./script 00:0C:29:FF:FE:0C:47:D5
00 0C 29 FF FE 0C 47 D5
Ishak
sumber
3

Berikut adalah solusi kecil yang dilakukan dengan POSIX /bin/sh(dalam kasus saya itu dash), sebuah fungsi yang berulang kali menggunakan ekspansi parameter (jadi tidak ada di IFSsini), dan menamai pipa, dan menyertakan noglobopsi untuk alasan yang disebutkan dalam jawaban Stephane .

#!/bin/sh
set -o noglob
get_ip_sections(){
    slice="$1"
    count=0
    while [ -n "${slice}" ] && [ "$count" -ne 4 ]
    do
        num="${slice%%.*}"
        printf '%s ' "${num}"
        slice="${slice#*${num}.}"
        count=$((count+1))
    done
}

ip="109.96.77.15"
named_pipe="/tmp/ip_stuff.fifo"
mkfifo "${named_pipe}"
get_ip_sections "$ip" > "${named_pipe}" &
read sec1 sec2 sec3 sec4 < "${named_pipe}"
printf 'Actual ip:%s\n' "${ip}"
printf 'Section 1:%s\n' "${sec1}"
printf 'Section 3:%s\n' "${sec3}"
rm  "${named_pipe}"

Ini berfungsi seperti itu:

$ ./get_ip_sections.sh 
Actual ip:109.96.77.15
Section 1:109
Section 3:77

Dan dengan ipberubah menjadi109.*.*.*

$ ./get_ip_sections.sh 
Actual ip:109.*.*.*
Section 1:109
Section 3:*

Penghitung loop dari 4 iterasi menyumbang 4 bagian dari alamat IPv4 yang valid, sementara akrobat dengan akun pipa bernama perlu untuk menggunakan bagian ip address lebih lanjut dalam skrip sebagai lawan memiliki variabel yang terjebak dalam subshell dari loop.

Sergiy Kolodyazhnyy
sumber
Saya telah memodifikasi jawaban Anda sehingga tidak menggunakan pipa dan menambahkannya ke jawaban saya yang ada .
G-Man Mengatakan 'Reinstate Monica'
0

Mengapa tidak menggunakan solusi sederhana dengan awk?

$ IP="192.168.1.1" $ echo $IP | awk -F '.' '{ print $1" "$2" "$3" "$4;}'

Hasil $ 192 168 1 1

Trường Giang Nguyễn
sumber
-2
$ ip_=192.168.2.3

$ echo $ip_ 

192.168.2.3

$ echo $ip_ |cut -d "." -f 1

192
Behrooz Mohamadi nasab
sumber
"... menggunakan ekspansi parameter ..."
Jeff Schaller
Dan di mana tiga lainnya?
G-Man Mengatakan 'Reinstate Monica'