web-dev-qa-db-ja.com

Bash:IPv4アドレスの4つのセクションの1つを抽出します

構文${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アドレスの2番目または3番目のセクションを抽出するにはどうすればよいですか?

これが私の解決策です:私は配列を使用して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

また、awksed、およびcutコマンドを使用していくつかの解決策を記述しました。

さて、私の質問は:配列とIFSの変更を使用しないパラメータ展開に基づくより簡単な解決策はありますか?

9
sci9

IFSのデフォルト値を想定して、各オクテットを独自の変数に抽出します。

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

または、次のように配列に入れます:

A=(${IP//./ })
16
Jason Musgrove

あなたの問題文は、あなたが意図したよりも少し寛大かもしれません。抜け穴を利用するリスクがある場合の解決策は次のとおりです muruが言及

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

これはやや不格好です。これは2つの使い捨て変数を定義しており、より多くのセクション(MACアドレスやIPv6アドレスなど)を処理するように簡単に調整することはできません。 Sergiy Kolodyazhnyyの答え は、上記を一般化してこれを一般化するように促しました:

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

これにより、sec1sec2sec3sec4が設定されます。これらは、

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ループは理解しやすいはずです。4回繰り返されます。
  • セルギーは、最初のソリューション(上記)でlast3last2の代わりとなる変数の名前としてsliceを選択しました。
  • declare sec"$count"="value"は、countsec1sec2sec3およびsec4である場合に、123および4に割り当てる方法です。 evalに少し似ていますが、より安全です。
  • value"${slice%%.*}"は、元の回答がfirstsecondおよびthirdに割り当てた値に類似しています。

DID一時的にIFSを再定義しないという解決策を具体的に求めたようですが、ここでは説明しなかった甘くてシンプルな解決策があるので、ここに示します。

IFS=. ; set -- $IP

この短いコマンドは、シェルの位置パラメータ$1$2$3$4にIPアドレスの要素を配置します。ただし、おそらく最初に元のIFSを保存し、後でそれを復元する必要があります。

知るか?多分あなたはその簡潔さと効率のためにこの答えを再考し、受け入れるでしょう。

(これは以前、誤ってIFS=. set -- $IPと指定されていました)

6
user1404316

easiestではなく、次のようなことができます。

_$ 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}_演算子の由来)、bash 4.3 +、busybox shyashmkshzshで機能しますが、もちろん zshには、より単純なアプローチがあります 。古いバージョンのbashでは、内部の引用符を削除する必要があります。他のほとんどのシェルで削除された内部引用符でも機能しますが、ksh93では機能しません。

これは、_$IP_にIPv4アドレスの有効な4進10進表記が含まれていることを前提としています(_0x6d.0x60.0x4d.0xf_(および一部のシェルでは8進)のような4進16進表記でも機能しますが、値を10進で出力します)。 _$IP_のコンテンツが信頼できないソースからのものである場合、それはコマンドインジェクションの脆弱性に相当します。

基本的に、_._のすべての_$IP_を_+256*(_に置き換えるため、最終的に次のように評価します。

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

したがって、IPv4アドレスのように4バイトから32ビット整数を構築しています(最終的にはバイトが逆になります)reversed、次に_>>_、_&_ビット演算子を使用して関連するバイトを抽出します。

valueの代わりに_${param+value}_標準演算子(ここでは常に設定されることが保証されている_$-_)を使用します。そうしないと、算術パーサーが括弧の不一致について文句を言うからです。ここのシェルは、開始_))_の終了_$((_を見つけることができ、その後内部で展開を実行して、評価する算術式。

と $(((${IP//./"+256*("}))))&255)) 代わりに、シェルは2番目と3番目の_)_ sを_))_の終了_$((_として扱い、構文エラーを報告します。

Ksh93では、次のことも実行できます。

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

bashmkshzshは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")
_

noglobは、_$IP_のような_10.*.*.*_の値の予期せぬ事態を回避するためのもので、サブシェルはこれらの変更の範囲をオプションと_$IFS_に制限します。


IPv IPv4アドレスは32ビット整数にすぎず、たとえば127.0.0.1は多くの(最も一般的な)テキスト表現の1つにすぎません。ループバックインターフェイスの同じ典型的なIPv4アドレスは、0x7f000001または127.1(ここでは、127.0/8クラスAネットワークの_1_アドレスであると言うためにより適切なアドレス)、または0177.0.1として表すこともできます。 8進数、10進数、または16進数として表される1から4の数値の他の組み合わせ。たとえば、それらすべてをpingに渡すことができ、それらがすべてlocalhostにpingすることがわかります。

bashまたは_$n_または_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オプションで8進定数を認識します)。

5

かしこまりました。象のゲームをしてみましょう。

$ 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
jthill

Zshを使用すると、パラメーター置換をネストできます。

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

これはbashでは不可能です。

4
muru

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)

これにより、テスト構文で正規表現一致の4つのキャプチャ括弧が作成されます。

[[ $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
3
Isaac

これは、POSIX /bin/sh(私の場合はdashです)、パラメーター拡張(ここではIFSは使用しない)、および名前付きパイプを使用して行われる小さなソリューションです。 noglobオプション Stephane's answer に記載されている理由。

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

そしてip109.*.*.*に変更

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

4反復のループ保持カウンターは有効なIPv4アドレスの4セクションを占めますが、名前付きパイプを使用したアクロバットは、ループのサブシェルで変数がスタックするのではなく、スクリプト内でIPアドレスのセクションをさらに使用する必要があることを説明します。

3
$ ip_=192.168.2.3

$ echo $ip_ 

192.168.2.3

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

192

Awkで単純なソリューションを使用しないのはなぜですか?

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

結果$ 192 168 1 1