AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / unix / 问题 / 420328
Accepted
sci9
sci9
Asked: 2018-01-29 19:58:46 +0800 CST2018-01-29 19:58:46 +0800 CST 2018-01-29 19:58:46 +0800 CST

Bash:提取 IPv4 地址的四个部分之一

  • 772

我们可以使用语法${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 更改的参数扩展的更简单的解决方案?

bash shell
  • 10 10 个回答
  • 12727 Views

10 个回答

  • Voted
  1. Best Answer
    Jason Musgrove
    2018-01-30T04:57:37+08:002018-01-30T04:57:37+08:00

    假设 IFS 的默认值,您将每个八位字节提取到它自己的变量中:

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

    或者进入一个数组:

    A=(${IP//./ })
    
    • 18
  2. user1404316
    2018-01-29T20:42:09+08:002018-01-29T20:42:09+08:00

    我确实意识到您专门要求了一个没有暂时重新定义的解决方案IFS,但是我有一个您没有涵盖的甜蜜而简单的解决方案,所以这里是:

    IFS=. ; set -- $IP
    

    这个简短的命令会将你的 IP 地址的元素放在 shell 的位置参数 $1, $2, $3, 中$4。但是,您可能希望先保存原始IFS文件,然后再恢复。

    谁知道?也许您会因其简洁和高效而重新考虑并接受此答案。

    (这以前被错误地给出为IFS=. set -- $IP)

    • 8
  3. G-Man Says 'Reinstate Monica'
    2018-01-29T20:26:30+08:002018-01-29T20:26:30+08:00

    您的问题陈述可能比您预期的要宽松一些。冒着利用漏洞的风险,这里是muru 提到的解决方案:

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

    这有点笨拙。它定义了两个一次性变量,并且不容易适应处理更多部分(例如,对于 MAC 或 IPv6 地址)。  Sergiy Kolodyaznyy 的回答启发我将上述内容概括为:

    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代替我的第一个解决方案(上图)。last3last2
    • declare sec"$count"="value"是一种分配给sec1,和 的方法, sec2,和. 这有点像,但更安全。sec3sec4count1234eval
    • , value,"${slice%%.*}"类似于我最初的答案分配给first,second和的值third。
    • 7
  4. Stéphane Chazelas
    2018-01-30T07:22:54+08:002018-01-30T07:22:54+08:00

    不是最简单的,但您可以执行以下操作:

    $ 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}运算符来自哪里)、 4.3+ 、bashbusybox和sh,当然,在 中,有更简单的方法。在旧版本中,您需要删除内引号。它也适用于在大多数其他 shell 中删除的那些内部引号,但不适用于 ksh93。yashmkshzshzshbash

    假设$IP包含 IPv4 地址的有效四进制表示(尽管这也适用于四十六进制表示0x6d.0x60.0x4d.0xf(在某些 shell 中甚至是八进制),但会以十进制输出值)。如果 的内容$IP来自不受信任的来源,那将构成命令注入漏洞。

    基本上,当我们用 替换每个.in$IP时+256*(,我们最终会评估:

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

    所以我们从这 4 个字节中构造一个 32 位整数,就像最终的 IPv4 地址一样(尽管字节颠倒了)¹,然后使用>>,&位运算符来提取相关字节。

    我们使用${param+value}标准运算符(这里$-保证始终设置)而不是仅仅value因为算术解析器会抱怨括号不匹配。这里的 shell 可以找到 open 的关闭)),$((然后在里面执行扩展,这将导致算术表达式求值。

    相反,$(((${IP//./"+256*("}))))&255))shell 会将)那里的第二个和第三个 s 视为关闭))并$((报告语法错误。

    在 ksh93 中,您还可以执行以下操作:

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

    bash, mksh,zsh 复制了 ksh93 的${var/pattern/replacement}操作符,但没有复制捕获组处理部分。zsh用不同的语法支持它:

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

    bash在其正则表达式匹配运算符中支持某种形式的捕获组处理,但在${var/pattern/replacement}.

    POSIXly,你会使用:

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

    为避免对like的noglob值造成意外,子shell 将这些更改的范围限制为 options 和.$IP10.*.*.*$IFS


    ¹ IPv4 地址只是一个 32 位整数,例如 127.0.0.1 只是众多(尽管是最常见的)文本表示形式之一。环回接口的同一个典型 IPv4 地址也可以表示为 0x7f000001 或 127.1(这里可能更合适的说法是它是1127.0/8 A 类网络上的地址),或 0177.0.1,或 1 的其他组合以八进制、十进制或十六进制表示的 4 个数字。例如,您可以将所有这些传递给ping您,您会看到它们都将 ping localhost。

    如果您不介意在or或or中设置任意临时变量(此处$n)的副作用,您可以简单地将所有这些表示转换回 32 位整数:bashksh93zsh -o octalzeroeslksh -o posix

    $((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识别八进制常量的选项)。

    • 5
  5. muru
    2018-01-29T20:49:48+08:002018-01-29T20:49:48+08:00

    使用 zsh,您可以嵌套参数替换:

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

    这在 bash 中是不可能的。

    • 4
  6. jthill
    2018-01-30T08:49:58+08:002018-01-30T08:49:58+08:00

    当然,让我们玩大象游戏。

    $ 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
    
    • 4
  7. user232326
    2018-01-30T18:04:57+08:002018-01-30T18:04:57+08:00

    与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)
    

    这将为测试语法中的正则表达式匹配创建四个捕获括号:

    [[ $IP =~ $regex ]]
    

    这允许在没有第一个组件(整个正则表达式匹配)的情况下打印数组 BASH_REMATCH:

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

    括号的数量会自动调整为匹配的字符串。因此,这将匹配 IPv6 地址的 MAC 或 EUI-64,尽管它们的长度不同:

    #!/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
    
    • 4
  8. Sergiy Kolodyazhnyy
    2018-01-30T22:45:06+08:002018-01-30T22:45:06+08:00

    这是一个使用 POSIX 完成的小解决方案/bin/sh(在我的情况下是dash),一个重复使用参数扩展的函数(所以IFS这里没有)和命名管道,并且由于Stephane 的回答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 次迭代的循环保持计数器占有效 IPv4 地址的 4 部分,而使用命名管道的杂技需要在脚本中进一步使用 ip 地址部分,而不是将变量卡在循环的子外壳中。

    • 3
  9. Trường Giang Nguyễn
    2020-04-03T19:12:41+08:002020-04-03T19:12:41+08:00

    为什么不使用 awk 的简单解决方案?

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

    结果 $ 192 168 1 1

    • 0
  10. Behrooz Mohamadi nasab
    2018-01-30T04:26:01+08:002018-01-30T04:26:01+08:00
    $ ip_=192.168.2.3
    
    $ echo $ip_ 
    
    192.168.2.3
    
    $ echo $ip_ |cut -d "." -f 1
    
    192
    
    • -2

相关问题

  • 从文本文件传递变量的奇怪问题

  • 虽然行读取保持转义空间?

  • 如何将带有〜的路径保存到变量中?

  • `tee` 和 `bash` 进程替换顺序

  • 运行一个非常慢的脚本直到它成功

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    ssh 无法协商:“找不到匹配的密码”,正在拒绝 cbc

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    如何卸载内核模块“nvidia-drm”?

    • 13 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Wong Jia Hau ssh-add 返回:“连接代理时出错:没有这样的文件或目录” 2018-08-24 23:28:13 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST
  • Martin Hope
    Bagas Sanjaya 为什么 Linux 使用 LF 作为换行符? 2017-12-20 05:48:21 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve