Bash: Trích xuất một trong bốn phần của địa chỉ IPv4


9

Chúng tôi có thể sử dụng cú pháp ${var##pattern}${var%%pattern}để trích xuất phần cuối cùng và đầu tiên của địa chỉ 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##*.}

Làm thế nào chúng ta có thể trích xuất phần thứ hai hoặc thứ ba của một địa chỉ IPv4 bằng cách sử dụng mở rộng tham số?

Đây là giải pháp của tôi: Tôi sử dụng một mảng và thay đổi biến 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

Ngoài ra tôi đã viết một số giải pháp sử dụng awk, sedcutcác lệnh.

Bây giờ, câu hỏi của tôi là: Có một giải pháp đơn giản hơn dựa trên việc mở rộng tham số không sử dụng thay đổi mảng và IFS không?


1
Bạn chỉ nên đặt IFScho readcó:IFS=. read -a ArrIP<<<"$IP"
muru

1
Không trong bash mà không sử dụng ít nhất nhiều biến. Một mở rộng tham số duy nhất không thể có được các thành phần thứ hai hoặc thứ ba. Zsh có thể lồng các tham số mở rộng, vì vậy nó có thể ở đó.
muru

@muru Bạn có thể vui lòng cung cấp giải pháp Zsh không?
sci9

4
Bạn có đảm bảo gì về việc bạn sẽ luôn giao dịch với các địa chỉ IP v4 và sẽ không bao giờ có địa chỉ IP v6?
Mawg nói rằng phục hồi Monica

4
Có một số lý do IFS=. read a b c d <<< "$IP"không được chấp nhận (nếu bạn đang sử dụng Bash, đó là)? Tại sao nó phải được thực hiện với việc mở rộng tham số?
ilkkachu

Câu trả lời:


16

Giả sử giá trị mặc định của IFS, bạn trích xuất mỗi octet thành biến riêng của nó với:

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

Hoặc vào một mảng với:

A=(${IP//./ })

2
+1. Dường như với tôi rằng đây là phương pháp đơn giản nhất, đơn giản nhất tuân theo các hạn chế của OP.
Chấn thương kỹ thuật số

7

Báo cáo vấn đề của bạn có thể tự do hơn một chút so với dự định của bạn. Có nguy cơ khai thác lỗ hổng, đây là giải pháp mà muru đã ám chỉ :

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

Điều này là hơi khó hiểu. Nó định nghĩa hai biến số bỏ đi và nó không dễ thích nghi để xử lý nhiều phần hơn (ví dụ: đối với địa chỉ MAC hoặc IPv6).  Câu trả lời của Sergiy Kolodyazhnyy đã truyền cảm hứng cho tôi để khái quát những điều trên cho điều này:

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

Bộ này sec1, sec2, sec3sec4, có thể được xác minh với

printf 'Section 1: %s\n' "$sec1"
printf 'Section 2: %s\n' "$sec2"
printf 'Section 3: %s\n' "$sec3"
printf 'Section 4: %s\n' "$sec4"
  • Các whilevòng lặp nên dễ hiểu - nó lặp bốn lần.
  • Sergiy chọn slicelàm tên cho một biến thay thế last3last2trong giải pháp đầu tiên của tôi (ở trên).
  • declare sec"$count"="value"là một cách để gán cho sec1, sec2, sec3sec4 khi count1, 2, 34. Nó giống như một chút eval, nhưng an toàn hơn.
  • Các value, "${slice%%.*}"là tương tự như giá trị chuyển nhượng câu trả lời ban đầu của tôi đến first, secondthird.

6

Tôi nhận ra rằng bạn đặc biệt yêu cầu một giải pháp mà DID KHÔNG tạm thời xác định lại IFS, nhưng tôi có một giải pháp đơn giản và ngọt ngào mà bạn không bao gồm, vì vậy hãy đi:

IFS=. ; set -- $IP

Đó là lệnh ngắn sẽ đưa các yếu tố của địa chỉ IP của bạn trong của vỏ tham số vị trí $1 , $2, $3, $4. Tuy nhiên, trước tiên bạn có thể muốn lưu bản gốc IFSvà khôi phục nó sau đó.

Ai biết? Có lẽ bạn sẽ xem xét lại và chấp nhận câu trả lời này vì tính ngắn gọn và hiệu quả của nó.

(Điều này trước đây được đưa ra không chính xác là IFS=. set -- $IP)


5
Tôi không nghĩ rằng nó hoạt động: nếu bạn thay đổi IFStrên cùng một dòng lệnh, giá trị mới sẽ không có hiệu lực khi các biến trên cùng một dòng lệnh được mở rộng. Tương tự như vớix=1; x=2 echo $x
ilkkachu

6
@chepner, nhưng một lần nữa, sethoàn toàn không sử dụng $IFS, $IFSchỉ được sử dụng cho việc tách từ $IP, nhưng ở đây, nó được gán quá muộn, do đó không có hiệu lực. Câu trả lời này về cơ bản là sai. IP=109.96.77.15 bash -c 'IFS=. set -- $IP; echo "$2"'đầu ra không có gì dù ở chế độ POSIX hay không. Bạn sẽ cần IFS=. command eval 'set -- $IP', hoặcIFS=. read a b c d << "$IP"
Stéphane Chazelas

4
Nó có thể phù hợp với bạn vì bạn đã đặt IFS thành .một trong những bài kiểm tra trước đây của bạn. Chạy IP=1.2.3.4 bash -xc 'IFS=. set $IP; echo "$2"'và bạn sẽ thấy nó không hoạt động. Và xem IP=1.2.3.4 bash -o posix -xc 'IFS=. set $IP; echo "\$1=$1 \$2=$2 IFS=$IFS"'để minh họa quan điểm của @ chepner.
Stéphane Chazelas

2
Sự vội vàng đã làm lãng phí, câu trả lời rõ ràng là sai và nên được sửa chữa để làm việc, vì đó vẫn là cách tôi làm điều đó, chỉ với nhiều mã hơn.
Lizardx

3
@ user1404316, bạn có thể đăng chính xác bộ lệnh bạn đã sử dụng để kiểm tra không? (Có lẽ là phiên bản vỏ của bạn.) Có bốn người dùng khác đã nói với bạn ở đây trong các nhận xét rằng nó không hoạt động như được viết trong câu trả lời. Với ví dụ.
ilkkachu

5

Không phải dễ nhất , nhưng bạn có thể làm một cái gì đó như:

$ 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

Điều đó sẽ làm việc trong ksh93 (nơi mà ${var//pattern/replacement}nhà điều hành xuất phát từ), bash4.3 trở lên, busybox sh, yash, mkshzsh, mặc dù tất nhiên trong zsh, có những cách tiếp cận đơn giản hơn nhiều . Trong các phiên bản cũ hơn bash, bạn cần xóa các trích dẫn bên trong. Nó hoạt động với những trích dẫn bên trong được loại bỏ trong hầu hết các shell khác, nhưng không phải là ksh93.

Giả định đó $IPchứa một biểu diễn thập phân bốn thập phân hợp lệ của một địa chỉ IPv4 (mặc dù điều đó cũng sẽ hoạt động cho các biểu diễn thập lục phân như 0x6d.0x60.0x4d.0xf(và thậm chí là bát phân trong một số vỏ) nhưng sẽ xuất ra các giá trị theo số thập phân). Nếu nội dung $IPđến từ một nguồn không đáng tin cậy, điều đó sẽ gây ra lỗ hổng tiêm lệnh.

Về cơ bản, như chúng ta đang thay thế mỗi .năm $IPvới +256*(, chúng tôi kết thúc đánh giá:

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

Vì vậy, chúng tôi đang xây dựng một số nguyên 32 bit ra khỏi những 4 byte như một địa chỉ IPv4 cuối cùng là (mặc dù với các byte đảo ngược) ¹ và sau đó sử dụng >>, &khai thác Bitwise để trích xuất các byte có liên quan.

Chúng tôi sử dụng ${param+value}toán tử tiêu chuẩn (ở đây $-được đảm bảo luôn được đặt) thay vì chỉ valuevì nếu không, trình phân tích cú pháp số học sẽ phàn nàn về dấu ngoặc đơn không khớp. Shell ở đây có thể tìm thấy sự đóng cửa ))cho việc mở $((, và sau đó thực hiện các mở rộng bên trong sẽ dẫn đến biểu thức số học để đánh giá.

Với $(((${IP//./"+256*("}))))&255))thay vào đó, vỏ sẽ đối xử thứ hai và thứ ba )ở đó như đóng cửa ))cho $((và báo cáo một lỗi cú pháp.

Trong ksh93, bạn cũng có thể làm:

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

bash, mksh, zsh Đã sao chép ksh93 của ${var/pattern/replacement}nhà điều hành nhưng không phải là chụp nhóm xử lý một phần. zshhỗ trợ nó với một cú pháp khác nhau:

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

bashkhông hỗ trợ một số hình thức xử lý nhóm chụp trong toán tử so khớp regrec của nó , nhưng không hỗ trợ${var/pattern/replacement} .

POSIXly, bạn sẽ sử dụng:

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

Để noglobtránh những bất ngờ xấu cho các giá trị $IPlike 10.*.*.*, subshell để giới hạn phạm vi của những thay đổi đó đối với các tùy chọn và $IFS.


Địa chỉ IPv4 chỉ là số nguyên 32 bit và ví dụ 127.0.0.1 chỉ là một trong nhiều cách trình bày văn bản (mặc dù phổ biến nhất). Địa chỉ IPv4 điển hình tương tự của giao diện loopback cũng có thể được biểu diễn dưới dạng 0x7f000001 hoặc 127.1 (có thể là 1địa chỉ thích hợp hơn ở đây để nói đó là địa chỉ trên mạng A lớp 127.0 / 8) hoặc 0177.0.1 hoặc các kết hợp khác của 1 đến 4 số được biểu thị dưới dạng bát phân, thập phân hoặc thập lục phân. Bạn có thể chuyển tất cả những thứ đó pingchẳng hạn và bạn sẽ thấy tất cả chúng sẽ ping localhost.

Nếu bạn không nhớ các tác dụng phụ của thiết lập một biến tạm thời tùy ý (ở đây $n), trong bashhay ksh93hay zsh -o octalzeroeshay lksh -o posix, bạn chỉ có thể chuyển đổi tất cả những cơ quan đại diện trở lại một số nguyên 32 bit với:

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

Và sau đó trích xuất tất cả các thành phần với >>/ &kết hợp như trên.

$ 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

mkshsử dụng các số nguyên 32 bit đã ký cho các biểu thức số học của nó, bạn có thể sử dụng $((# n=32,...))ở đó để buộc sử dụng các số 32 bit không dấu (và posixtùy chọn cho nó để nhận ra các hằng số bát phân).


Tôi hiểu khái niệm lớn, nhưng tôi chưa bao giờ thấy ${-+trước đây. Tôi cũng không thể tìm thấy bất kỳ tài liệu nào về nó. Nó hoạt động, nhưng tôi chỉ tò mò xác nhận, có phải chỉ để biến chuỗi thành một biểu thức toán học? Tôi có thể tìm định nghĩa chính thức ở đâu? Ngoài ra, các trích dẫn bổ sung bên trong phần thay thế mở rộng tham số không hoạt động trong GNU bash, phiên bản 4.1.2 (2) - phát hành CentOS 6.6. Tôi đã phải làm điều này thay vào đóecho "$((${-+"(${IP//./+256*(}))))"}>>16&255))"
Levi Uzodike

1
@LeviUzodike, xem chỉnh sửa.
Stéphane Chazelas

4

Với zsh, bạn có thể lồng thay thế tham số:

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

Điều này là không thể trong bash.


1
Trong zsh, bạn có thể thích${${(s(.))ip}[3]}
Stéphane Chazelas

4

Chắc chắn, chúng ta hãy chơi trò chơi con voi.

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

hoặc là

$ ipsplit() { local IFS=.; echo $*; }
$ set -- `ipsplit 10.1.2.3`
$ echo $1
10

2
"Trò chơi con voi" là gì?
tự đại diện

1
@Wildcard là một kiểu chơi chữ / trò đùa hình elip, nó là một tài liệu tham khảo cho cả người mù mô tả câu chuyện về con voi, có những phần để xem xét mọi người sẽ có ý kiến ​​riêng của họ, và theo phong tục cổ xưa của một vị vua muốn tặng quà với upkeep lo xa tốn kém, làm mềm qua nhiều thế kỷ để những món quà có giá trị chỉ mơ hồ mà có xu hướng được regifted cho giá trị giải trí .. Cả hai dường như áp dụng ở đây :-)
jthill

3

Với IP=12.34.56.78.

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

#!/bin/bash
IP=$1

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

Sự miêu tả:

Sử dụng mở rộng tham số ${IP// }để chuyển đổi từng dấu chấm trong ip thành dấu ngoặc đơn mở một dấu chấm và dấu ngoặc đơn đóng. Thêm dấu ngoặc đơn ban đầu và dấu ngoặc đơn đóng, chúng ta sẽ nhận được:

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

sẽ tạo ra bốn dấu ngoặc đơn cho khớp regex trong cú pháp kiểm tra:

[[ $IP =~ $regex ]]

Điều đó cho phép in mảng BASH_REMATCH mà không cần thành phần đầu tiên (toàn bộ khớp regex):

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

Số lượng dấu ngoặc đơn được tự động điều chỉnh theo chuỗi khớp. Vì vậy, điều này sẽ khớp với MAC hoặc EUI-64 của địa chỉ IPv6 mặc dù chúng có độ dài khác nhau:

#!/bin/bash
IP=$1

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

Sử dụng nó:

$ ./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

3

Đây là một giải pháp nhỏ được thực hiện với POSIX /bin/sh(trong trường hợp của tôi là vậy dash), một chức năng liên tục sử dụng mở rộng tham số (không có IFSở đây) và đặt tên ống, và bao gồm noglobtùy chọn cho các lý do được đề cập trong câu trả lời của 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}"

Điều này hoạt động như vậy:

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

ipđã đổi thành109.*.*.*

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

Bộ đếm vòng lặp của 4 lần lặp chiếm 4 phần của địa chỉ IPv4 hợp lệ, trong khi nhào lộn với tài khoản ống có tên cần sử dụng thêm các phần của địa chỉ ip trong tập lệnh thay vì có các biến bị mắc kẹt trong một chuỗi con của vòng lặp.


Tôi đã sửa đổi câu trả lời của bạn để nó không sử dụng một đường ống và thêm nó vào câu trả lời hiện có của tôi .
G-Man nói 'Phục hồi Monica'

0

Tại sao không sử dụng giải pháp đơn giản với awk?

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

Kết quả $ 192 168 1 1


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.