概要
WSL2 で ip adder コマンドから IPアドレスを取得したくなることがる。通常は awk など使うが、他の言語だとどうやるのか気になったので試してみた。
それぞれの言語の特徴を垣間見られる点で興味深い。
環境
- Ubuntu 18.04 LTS
表示内容
ポイント
- IPv4 アドレスは
inet
の行のみにある -
inet
とinet6
の行があるので行を抽出する場合は区別が必要
$ ip address show dev eth0
...
inet xxx.xxx.xxx.xxx/24 brd xxx.xxx.xxx.255 scope global dynamic
valid_lft forever preferred_lft forever
inet6 xxxx:xxxx:xxx:xxxx:xxxx:xxxx:xxxx:xxxx/64 scope global dynamic
valid_lft 86398sec preferred_lft 14398sec
...
結局どれが一番いいの?
この記事の趣旨ではないけれど、どれが一番いいの?ということなら、awk かなと思います。結果を見ながらコマンドを組み立てられるし、正規表現の知識もいらないためです。
ip address show dev eth0 | awk '/inet / {print $2}' | awk -F/ '{print $1}'
awk のパターンマッチングなどの使い方を勉強したくない、このコマンドが理解しやすいでしょうか。
ip address show dev eth0 | grep -w inet | awk '{print $2}' | cut -d/ -f 1
One-linersについて
- メリット
- ファイルを配置しておく必要がない
- デメリット
- (コードによるが)可読性が低い
全体的な指針
大まかに次のアプローチになる
- 行と列を意識する
ip address show dev eth0 → 行を抽出 → 列を抽出 → サブネットを削る - 行を抽出した後、正規表現でIPアドレスを抜く
ip address show dev eth0 → 行を抽出 → 正規表現でIPアドレスを抽出 - プログラミング言語のソケット関係のクラスを通じて取得する
grep版
ip address show dev eth0 | grep -Eo '([0-9]+(\.[0-9]+){3})' | head -1
awk版
全部 awk でやるには (将来的にメンテナンスするであろう第三者を含めて) awk のことを色々と知らなくてはならないので、妥当でないことはある。
awkのみ
以下、正規表現を使わない例。
ip address show dev eth0 | awk '/inet / {print $2}' | awk -F/ '{print $1}'
以下、正規表現を使った例。
ip address show dev eth0 | awk '/inet / {gsub(/\/[0-9]*/, "", $2); print $2}'
awk以外のコマンドを組み合わせる
-
grep -w inet
は単語としての照合を行うのでinet6
の行は除外される。 - netmask を削る場合、 cut や sed が使える
ip address show dev eth0 | grep -w inet | awk '{print $2}' | cut -d/ -f 1
ip address show dev eth0 | grep -w inet | awk '{print $2}' | sed -e 's@/.*@@'
sed版
正規表現をしっていれば sed も使えますね。
awk知らんし覚えたくもない…という人には良いかもしれません。
ip address show dev eth0 | sed -ne '/inet /p' | sed -e 's@.*inet \([0-9.]*\)/.*@\1@'
grep版
IPv4アドレスのパターンマッチする箇所は2か所あるので、最初に見つかったものだけ取り出す。
grep -w inet
は今回は無くても良いが grep -o
が行と列を区別することなく抜いてくるので grep -w inet
と行を絞り込んでおくほうが無難。
ip address show dev eth0 | grep -w inet | grep -Eo '[0-9]+(\.[0-9]+){3}' | head -1
Ruby版
puts の出力以外はメソッドチェーンが効くので見通しは良いほう。ただ、OS自体の必須パッケージになっていないことも多いので、使えるかどうかは環境次第。
標準入力から読み取る場合
ip address show dev eth0 | ruby -e 'puts STDIN.read.scan(/inet ([0-9.]+)/)'
以下、色々書き方があるということで参考。
書く人とメンテナンスに関わる人が分かりやすければ、特に短くすることにこだわる必要はないが、テクニックをただ知らないだけなのと、知っていて使わないのとはまた別なので、一応。
配列型をなるべく残した書き方
Haskellをやっていると演算の合成を意識した書き方で可読性が高まることがある。RubyでいうOne-linerでは配列から文字列を取り出す部分の [n]
の演算で作成時にコード誤りを起こしやすい。(どちらかというと、出来上がってからというより変更するときに間違えやすい)
ip address show dev eth0 | ruby -e 'puts STDIN.read.split("\n").select{|s| /inet\W/ =~ s}.map{|s| s.split(%r@\s+|/@)[2]}[0]'
上記を短くする書き方
Array#filter
の代わりに Enumerable#grep
を使うともう少し短くなる。
ip address show dev eth0 | ruby -e 'puts STDIN.readlines.grep(/inet\W/)[0].split(%r@\s+|/@)[2]'
コマンド実行の場合
ruby -e 'puts IO.popen("ip address show dev eth0") {|io| io.read}.scan(/inet ([0-9.]+)/)'
ruby -e 'puts IO.popen("ip address show dev eth0") {|io| io.read}.split("\n").filter{|s| /inet\W/ =~ s}.map{|s| s.split(%r@\s+|\/@)[2]}[0]'
クラスを使う
今回は必ず目当ての IP が見つかるので select でなく find にした。
ruby -r socket -e "puts Socket.getifaddrs.find{_1.name == 'eth0' and _1.addr.ipv4?}.addr.ip_address"
Python版
- メソッドチェーンが使えず関数呼び出しが必要となることが多い。
- Python 自体は、OSの必須パッケージとなっていることが多く強みがある
一方で古い環境では Python3 が入っていなかったりするし、python3 のコマンドやパスは環境によって異なることで煩わしく思うこともある - Pythonはサードパーティーのパッケージを導入するとより簡潔に取得できる (環境変更が容易ならこれも選択肢の一つ)
標準入力から読み取る場合
ip address show dev eth0 | python -c "import re, sys; print(re.findall('[ ]+inet (.*)/[0-9]'
, sys.stdin.read())[0])"
コマンド実行の場合
コマンドから読み取る場合は、バイト文字列となるので Unicode文字列に変換する必要がある。
python -c "import re, subprocess; print(re.findall('[ ]+inet (.*)/[0-9]', subprocess.run('ip address show dev eth0'.split(), stdout=subprocess.PIPE).stdout.decode('utf-8'))[0])"
標準ライブラリ中のクラスを使う
connect するのが気持ち悪いですが、組み込みのクラスだけで済ます場合は可読性が高めな選択肢は少なくなる。
python -c 'import fcntl, socket, struct; s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.connect(("8.8.8.8", 80)); print(s.getsockname()[0])'
あとがき
難しくないにしても grep / awk / sed などのコマンドの知識をどこまで要求するかというのは気になるところです。最近はアプリ畑から入った人は Python 知っているけどシェルは知らんという人も今はいそうなので。個人的にはシェルは知っているので、こういうインフラ寄りのは基本は awk 中心に組むと思います。
One-linersに関しては、メソッドチェーンの関係でRubyは組みやすい傾向にあります。プログラミングが複雑化しそうな状況では、人や環境が許せば Ruby もいいかなと思います。やはり、文字列処理は書きやすいです。
今回一番組みにくかったのが Python です。Pythonだけができるプログラマだけなら選択肢に入ってきますが、サードパーティーのパッケージは時が経つと非公開になることもあるので、今回は一番最後に使いたい言語です。