Bash: แยกหนึ่งในสี่ส่วนของที่อยู่ IPv4


9

เราสามารถใช้ไวยากรณ์${var##pattern}และ${var%%pattern}แยกส่วนสุดท้ายและส่วนแรกของที่อยู่ 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##*.}

เราจะแยกส่วนที่สองหรือสามของที่อยู่ IPv4 โดยใช้การขยายพารามิเตอร์ได้อย่างไร

นี่คือโซลูชันของฉัน: ฉันใช้อาร์เรย์และเปลี่ยนตัวแปร 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

นอกจากนี้ผมได้เขียนแก้ปัญหาบางอย่างใช้awk, sedและcutคำสั่ง

ตอนนี้คำถามของฉันคือ: มีวิธีที่ง่ายขึ้นตามการขยายตัวพารามิเตอร์ที่ไม่ได้ใช้อาร์เรย์และ IFS เปลี่ยน?


1
คุณควรตั้งค่าไว้เฉพาะIFSที่readนั่น:IFS=. read -a ArrIP<<<"$IP"
muru

1
ไม่ได้อยู่ใน bash โดยไม่ใช้ตัวแปรหลายตัวเป็นอย่างน้อย การขยายพารามิเตอร์เดี่ยวไม่สามารถรับองค์ประกอบที่สองหรือสาม Zsh สามารถซ้อนพารามิเตอร์การขยายดังนั้นมันอาจเป็นไปได้ที่นั่น
muru

@muru คุณช่วยแก้ปัญหา Zsh ได้ไหม?
sci9

4
คุณมีการรับประกันอะไรบ้างที่คุณจะต้องจัดการกับที่อยู่ IP v4 และจะไม่มีที่อยู่ IP v6 อยู่เสมอ
Mawg กล่าวว่าคืนสถานะโมนิก้า

4
มีเหตุผลบางอย่างที่IFS=. read a b c d <<< "$IP"ไม่เป็นที่ยอมรับ (ถ้าคุณใช้ Bash นั่นคือ) เหตุใดจึงต้องทำกับการขยายพารามิเตอร์?
ilkkachu

คำตอบ:


16

สมมติว่าค่าเริ่มต้นของ IFS คุณแยกแต่ละอ็อกเท็ตไปเป็นตัวแปรของตัวเองด้วย:

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

หรือเป็นอาร์เรย์ด้วย:

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

2
+1 ฉันคิดว่านี่เป็นวิธีที่ง่ายและตรงไปตรงมาที่สุดที่ปฏิบัติตามข้อ จำกัด ของ OPs
Digital Trauma

7

คำแถลงปัญหาของคุณอาจเสรีกว่าที่คุณตั้งใจสักเล็กน้อย ความเสี่ยงในการหาช่องโหว่ที่นี่คือคำตอบของmuru ที่พูดพาดพิงถึง :

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

นี่เป็นเรื่องที่ค่อนข้างไม่แน่นอน มันกำหนดตัวแปรสองตัวที่ละทิ้งไปและมันไม่ได้ถูกปรับให้พร้อมรับมือกับส่วนอื่น ๆ (เช่นสำหรับที่อยู่ MAC หรือ IPv6)  คำตอบของ Sergiy Kolodyazhnyy เป็นแรงบันดาลใจให้ฉันพูดคุยเกี่ยวกับเรื่องนี้:

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

ชุดนี้sec1, sec2, sec3และsec4ซึ่งสามารถตรวจสอบได้ด้วย

printf 'Section 1: %s\n' "$sec1"
printf 'Section 2: %s\n' "$sec2"
printf 'Section 3: %s\n' "$sec3"
printf 'Section 4: %s\n' "$sec4"
  • การwhileวนซ้ำควรเข้าใจง่าย - มันวนซ้ำสี่ครั้ง
  • Sergiy เลือกsliceเป็นชื่อของตัวแปรที่ใช้แทนlast3และlast2ในโซลูชันแรกของฉัน (ด้านบน)
  • declare sec"$count"="value"เป็นวิธีที่จะกำหนดให้sec1, sec2, sec3และsec4 เมื่อcountเป็น1, 2, และ3 4มันเป็นเรื่องเล็กน้อยevalแต่ปลอดภัยกว่า
  • value, "${slice%%.*}"เป็นคล้ายกับค่ากำหนดคำตอบเดิมของฉันไปfirst, และsecondthird

6

ฉันรู้ว่าคุณถามหาวิธีแก้ปัญหาที่ไม่ได้กำหนดใหม่ชั่วคราวIFSแต่ฉันมีวิธีแก้ปัญหาที่ง่ายและหวานที่คุณไม่ได้ครอบคลุมดังนั้นที่นี่จะไป:

IFS=. ; set -- $IP

ว่าคำสั่งสั้น ๆ จะทำให้องค์ประกอบของที่อยู่ IP ของคุณในเปลือกของพารามิเตอร์ตำแหน่ง $1 , $2, ,$3 $4อย่างไรก็ตามคุณอาจต้องการบันทึกต้นฉบับก่อนIFSและเรียกคืนในภายหลัง

ใครจะรู้? บางทีคุณอาจพิจารณาใหม่และยอมรับคำตอบนี้เพื่อความกระชับและประสิทธิภาพ

(ซึ่งก่อนหน้านี้ได้รับอย่างไม่ถูกต้องเป็นIFS=. set -- $IP)


5
ฉันไม่คิดว่ามันใช้งานได้: ถ้าคุณเปลี่ยนIFSในบรรทัดคำสั่งเดียวกันค่าใหม่จะไม่มีผลเมื่อตัวแปรในบรรทัดคำสั่งเดียวกันนั้นขยายออก เช่นเดียวกับx=1; x=2 echo $x
ilkkachu

6
@chepner แต่อีกครั้งsetไม่ได้ใช้$IFSเลย$IFSใช้สำหรับการแยกคำ$IPเท่านั้น แต่ที่นี่มันสายเกินไปแล้วจึงไม่มีผล คำตอบนี้ผิดโดยทั่วไป IP=109.96.77.15 bash -c 'IFS=. set -- $IP; echo "$2"'แสดงผลไม่ว่าจะอยู่ในโหมด POSIX หรือไม่ คุณต้องการIFS=. command eval 'set -- $IP'หรือIFS=. read a b c d << "$IP"
Stéphane Chazelas

4
อาจเป็นไปได้สำหรับคุณเนื่องจากคุณตั้งค่า IFS เป็น.หนึ่งในการทดสอบก่อนหน้านี้ เรียกใช้IP=1.2.3.4 bash -xc 'IFS=. set $IP; echo "$2"'แล้วคุณจะเห็นว่ามันใช้งานไม่ได้ และดูIP=1.2.3.4 bash -o posix -xc 'IFS=. set $IP; echo "\$1=$1 \$2=$2 IFS=$IFS"'ตัวอย่างของจุด @ chepner
Stéphane Chazelas

2
รีบทำให้เสียคำตอบก็ดูเหมือนจะผิดและควรได้รับการแก้ไขให้ทำงานเนื่องจากมันยังเป็นวิธีที่ฉันจะทำโดยใช้โค้ดเพิ่มเติมเท่านั้น
Lizardx

3
@ user1404316 คุณสามารถโพสต์ชุดคำสั่งที่แน่นอนที่คุณใช้ในการทดสอบได้หรือไม่ (อาจรวมถึงเวอร์ชันของเชลล์ของคุณ) มีผู้ใช้อีกสี่คนที่เคยบอกคุณที่นี่ในความคิดเห็นว่ามันไม่ทำงานตามที่เขียนไว้ในคำตอบ ด้วยตัวอย่าง
ilkkachu

5

ไม่ใช่วิธีที่ง่ายที่สุดแต่คุณสามารถทำสิ่งต่อไปนี้:

$ 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

ว่าควรจะทำงานใน ksh93 (ที่ว่า${var//pattern/replacement}ผู้ประกอบการมาจาก) bash4.3+, busybox sh, yash, mkshและzshแม้ว่าแน่นอนในzshวิธีการที่มีที่ง่ายมาก ในเวอร์ชันที่เก่ากว่าbashคุณจะต้องลบคำพูดภายใน มันใช้งานได้กับอัญประกาศภายในเหล่านั้นในเชลล์อื่น ๆ ส่วนใหญ่เช่นกัน แต่ไม่ใช่ ksh93

สมมติว่า$IPมีการแทนค่า quad-decimal ที่ถูกต้องของที่อยู่ IPv4 (แม้ว่ามันจะใช้ได้สำหรับการแทน quad-hexadecimal เช่น0x6d.0x60.0x4d.0xf(และแม้แต่เลขฐานแปดในบางเชลล์) แต่จะเอาท์พุทค่าเป็นทศนิยม) หากเนื้อหาของ$IPมาจากแหล่งที่ไม่น่าเชื่อถือนั่นจะทำให้เกิดช่องโหว่ในการฉีดคำสั่ง

โดยทั่วไปในขณะที่เรากำลังแทนที่ทุก.ใน$IPกับ+256*(เราจบลงด้วยการประเมินผล:

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

ดังนั้นเราจึงกำลังสร้างจำนวนเต็ม 32 บิตออกจากผู้ที่ 4 ไบต์เช่นที่อยู่ IPv4 ในท้ายที่สุดคือ (แม้ว่าจะมีไบต์กลับ) ¹แล้วใช้>>, &ผู้ประกอบการที่จะดึงบิตไบต์ที่เกี่ยวข้อง

เราใช้ตัว${param+value}ดำเนินการมาตรฐาน (ที่นี่$-ซึ่งรับประกันว่าจะถูกตั้งค่าเสมอ) แทนที่จะเป็นเพียงvalueเพราะมิฉะนั้นตัวแยกวิเคราะห์ทางคณิตศาสตร์จะบ่นเกี่ยวกับวงเล็บที่ไม่ตรงกัน เปลือกที่นี่สามารถหาปิด))สำหรับการเปิด$((และจากนั้นดำเนินการขยายที่อยู่ภายในที่จะมีผลในการแสดงออกทางคณิตศาสตร์ในการประเมิน

ด้วย$(((${IP//./"+256*("}))))&255))แทนเปลือกจะรักษาที่สองและสาม)ของที่นั่นในฐานะปิด))สำหรับการ$((และรายงานข้อผิดพลาดทางไวยากรณ์

ใน ksh93 คุณสามารถทำได้:

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

bash, mksh, zsh ได้คัดลอก ksh93 ของ${var/pattern/replacement}ผู้ประกอบการ แต่ไม่ว่าเป็นส่วนหนึ่งในการจัดการการจับภาพกลุ่ม zshสนับสนุนด้วยไวยากรณ์ที่แตกต่าง:

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

bashไม่สนับสนุนรูปแบบบางส่วนของกลุ่มการจับภาพในการจัดการผู้ประกอบการ regexp ตรงกัน${var/pattern/replacement}แต่ไม่ได้อยู่ใน

POSIXly คุณจะใช้:

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

noglobเพื่อหลีกเลี่ยงความผิดที่ไม่ดีสำหรับค่า$IPเช่น10.*.*.*, subshell ที่จะ จำกัด $IFSขอบเขตของการเปลี่ยนแปลงเหล่านั้นกับตัวเลือกและ


IPv ที่อยู่ IPv4 เป็นจำนวนเต็ม 32 บิตและตัวอย่าง 127.0.0.1 เป็นเพียงหนึ่งในหลาย ๆ ข้อความ ที่อยู่ IPv4 ทั่วไปเดียวกันนั้นของอินเทอร์เฟซแบบวนกลับสามารถแสดงเป็น 0x7f000001 หรือ 127.1 (อาจเป็นที่ที่เหมาะสมกว่าที่นี่เพื่อบอกว่ามันเป็นที่1อยู่ในเครือข่ายคลาส A 127.0 / 8) หรือ 0177.0.1 หรือชุดอื่น ๆ ของ 1 ตัวเลข 4 ถึงแสดงเป็นฐานแปดทศนิยมหรือเลขฐานสิบหก คุณสามารถส่งต่อสิ่งเหล่านั้นทั้งหมดไปยังpingและคุณจะเห็นว่าพวกเขาทั้งหมดจะ ping localhost

หากคุณไม่คำนึงถึงผลข้างเคียงของการตั้งค่าตัวแปรชั่วคราวแบบสุ่ม (ที่นี่$n) ในbashหรือksh93หรือzsh -o octalzeroesหรือlksh -o posixคุณสามารถแปลงการแทนค่าทั้งหมดเหล่านั้นกลับไปเป็นจำนวนเต็ม 32 บิตด้วย:

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

จากนั้นแยกส่วนประกอบทั้งหมดด้วย>>/ &ชุดเหมือนด้านบน

$ 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

mkshใช้จำนวนเต็ม 32 บิตที่มีลายเซ็นสำหรับนิพจน์ทางคณิตศาสตร์คุณสามารถใช้ที่$((# n=32,...))นั่นเพื่อบังคับให้ใช้ตัวเลข 32 บิตที่ไม่ได้ลงชื่อ (และposixตัวเลือกสำหรับให้รับรู้ค่าคงที่แปด)


ฉันเข้าใจแนวคิดที่ยิ่งใหญ่ แต่ฉันไม่เคยเห็น${-+มาก่อน ฉันไม่พบเอกสารใด ๆ เลย มันใช้งานได้ แต่ฉันแค่อยากรู้อยากเห็นเพื่อยืนยันว่ามันจะเปลี่ยนสตริงให้เป็นนิพจน์ทางคณิตศาสตร์หรือไม่? ฉันจะหาคำจำกัดความที่เป็นทางการได้จากที่ไหน นอกจากนี้ใบเสนอราคาพิเศษภายในส่วนการแทนที่การขยายพารามิเตอร์ไม่ทำงานใน GNU bash เวอร์ชัน 4.1.2 (2) - ปล่อย CentOS 6.6 ฉันต้องทำสิ่งนี้แทนecho "$((${-+"(${IP//./+256*(}))))"}>>16&255))"
Levi Uzodike

1
@LeviUzodike ดูการแก้ไข
Stéphane Chazelas

4

ด้วย zsh คุณสามารถซ้อนการแทนที่พารามิเตอร์:

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

นี่เป็นไปไม่ได้ในการทุบตี


1
ในzshคุณอาจต้องการ${${(s(.))ip}[3]}
Stéphane Chazelas

4

แน่นอนว่ามาเล่นเกมช้างกันเถอะ

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

หรือ

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

2
"เกมช้าง" คืออะไร?
สัญลักษณ์แทน

1
@Wildcard เป็นรูปวงรีเล่นสำนวน / ตลกมันเป็นทั้งการอ้างอิงถึงคนตาบอด - อธิบายเรื่องราวช้างมีส่วนที่อาจจะดูทุกคนจะมีของตัวเองและประเพณีโบราณของกษัตริย์ที่ต้องการ จะให้ของขวัญที่มีค่าบำรุงรักษาแพงแหลกนิ่มกว่าศตวรรษของขวัญของค่าพิรุธเพียงว่ามีแนวโน้มที่จะ regifted ค่าความบันเทิง .. ทั้งสองดูเหมือนจะใช้ที่นี่ :-)
jthill

3

IP=12.34.56.78กับ

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

และ

#!/bin/bash
IP=$1

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

รายละเอียด:

ใช้การขยายพารามิเตอร์${IP// }เพื่อแปลงแต่ละจุดใน ip เป็นเครื่องหมายวงเล็บเปิดจุดและวงเล็บปิด การเพิ่มวงเล็บเริ่มต้นและวงเล็บปิดเราจะได้รับ:

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

ซึ่งจะสร้างวงเล็บการจับสี่รายการสำหรับการจับคู่ regex ในไวยากรณ์ทดสอบ:

[[ $IP =~ $regex ]]

ที่ช่วยให้การพิมพ์ของอาร์เรย์ BASH_REMATCH โดยไม่ต้องมีองค์ประกอบแรก (การแข่งขัน regex ทั้งหมด):

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

จำนวนวงเล็บจะถูกปรับเป็นสตริงที่ตรงกันโดยอัตโนมัติ ดังนั้นสิ่งนี้จะจับคู่ทั้ง MAC หรือ EUI-64 ของที่อยู่ IPv6 แม้จะมีความยาวแตกต่างกัน:

#!/bin/bash
IP=$1

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

ใช้มัน:

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

นี่เป็นทางออกที่มีขนาดเล็กทำด้วย POSIX /bin/sh(ในกรณีของฉันว่าdash) ฟังก์ชั่นที่ใช้ซ้ำการขยายตัวพารามิเตอร์ (เพื่อให้ไม่มีIFSที่นี่) และท่อชื่อและรวมถึงnoglobตัวเลือกด้วยเหตุผลที่กล่าวถึงในคำตอบของสเตฟาน

#!/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}"

วิธีนี้ใช้ได้ผล:

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

และด้วยการipเปลี่ยนเป็น109.*.*.*

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

การวนลูปรักษาตัวนับ 4 บัญชีวนซ้ำสำหรับ 4 ส่วนของที่อยู่ IPv4 ที่ถูกต้องในขณะที่การแสดงผาดโผนที่มีชื่อไปป์บัญชีจำเป็นต้องใช้ส่วนของที่อยู่ IP ภายในสคริปต์แทนการมีตัวแปรติดอยู่ใน subshell ของลูป


ฉันได้แก้ไขคำตอบของคุณจึงไม่ใช้ท่อและเพิ่มไปยังคำตอบมีอยู่ของฉัน
G-Man กล่าวว่า 'Reinstate Monica'


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.