構文${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
また、awk
、sed
、およびcut
コマンドを使用していくつかの解決策を記述しました。
さて、私の質問は:配列とIFSの変更を使用しないパラメータ展開に基づくより簡単な解決策はありますか?
IFSのデフォルト値を想定して、各オクテットを独自の変数に抽出します。
read A B C D <<<"${IP//./ }"
または、次のように配列に入れます:
A=(${IP//./ })
あなたの問題文は、あなたが意図したよりも少し寛大かもしれません。抜け穴を利用するリスクがある場合の解決策は次のとおりです 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
これにより、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
ループは理解しやすいはずです。4回繰り返されます。last3
とlast2
の代わりとなる変数の名前としてslice
を選択しました。declare sec"$count"="value"
は、count
がsec1
、sec2
、sec3
およびsec4
である場合に、1
、2
、3
および4
に割り当てる方法です。 eval
に少し似ていますが、より安全です。value
、"${slice%%.*}"
は、元の回答がfirst
、second
およびthird
に割り当てた値に類似しています。DID一時的にIFS
を再定義しないという解決策を具体的に求めたようですが、ここでは説明しなかった甘くてシンプルな解決策があるので、ここに示します。
IFS=. ; set -- $IP
この短いコマンドは、シェルの位置パラメータ$1
、$2
、$3
、$4
にIPアドレスの要素を配置します。ただし、おそらく最初に元のIFS
を保存し、後でそれを復元する必要があります。
知るか?多分あなたはその簡潔さと効率のためにこの答えを再考し、受け入れるでしょう。
(これは以前、誤ってIFS=. set -- $IP
と指定されていました)
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 sh
、yash
、mksh
、zsh
で機能しますが、もちろん 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}
_標準演算子(ここでは常に設定されることが保証されている_$-
_)を使用します。そうしないと、算術パーサーが括弧の不一致について文句を言うからです。ここのシェルは、開始_))
_の終了_$((
_を見つけることができ、その後内部で展開を実行して、評価する算術式。
と 代わりに、シェルは2番目と3番目の_$(((${IP//./"+256*("}))))&255))
)
_ 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")
_
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進定数を認識します)。
かしこまりました。象のゲームをしてみましょう。
$ 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
Zshを使用すると、パラメーター置換をネストできます。
$ ip=12.34.56.78
$ echo ${${ip%.*}##*.}
56
$ echo ${${ip#*.}%%.*}
34
これはbashでは不可能です。
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
これは、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
そしてip
を109.*.*.*
に変更
$ ./get_ip_sections.sh
Actual ip:109.*.*.*
Section 1:109
Section 3:*
4反復のループ保持カウンターは有効なIPv4アドレスの4セクションを占めますが、名前付きパイプを使用したアクロバットは、ループのサブシェルで変数がスタックするのではなく、スクリプト内でIPアドレスのセクションをさらに使用する必要があることを説明します。
$ 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