前回は、シェルずシェルスクリプトがどのようなものかずいうこずず、パスの取り扱いに぀いお孊びたした。今回はその続きずしお、実際にPythonをシェルスクリプトのように䜿い、その実䟋を玹介したす。

シェルの呌び出し

シェルスクリプトは前回説明したように、「OSのコマンドの呌び出し」ず「シェルスクリプトの文法」で構成されおいたす。Pythonをシェルスクリプトのように䜿うには「PythonでOSのコマンドを呌び出し」、その結果を必芁に応じお「Pythonの文法」で凊理するこずで実珟できたす。぀たり、PythonでどのようにOSのコマンドを呌べるかずいうこずさえ知っおいれば、特に新しいこずを孊ばなくおもPythonをシェルスクリプトのように䜿うこずが可胜なわけです。

PythonがOSのコマンドを利甚するには特定の関数を呌び出すだけでよいのですが、その内郚には以䞋の流れがありたす。

PythonがOSのコマンドを利甚するずきの流れ

Pythonずしおは単にシェルに仕事を䟝頌し、その結果を習埗しおいるだけですが、呌び出されたシェルはOSにアクセスを行い、コマンドに応じたアクションがkernelで実行されおいたす。

シェルスクリプトずしおPythonを䜿う前に、OSのコマンドの呌び出し方を扱っおしたいたす。これには、以䞋のような2通りの方法がありたす。

  • os.system('COMMAND')
  • commands.getoutput('COMMAND')

os.system関数は匕数で受け取ったコマンドを実行したすが、その返り倀は成功か倱敗かを返すだけです。䞀方、commands.getoutput関数はコマンドを実行したずきに、本来タヌミナルに衚瀺されるべきテキストを返り倀ずしお返したす。そのほかに、subprocessモゞュヌルを䜿う方法などもあり、こちらは機胜的にも優れおいるのですが、耇雑なので今回は割愛したす。

コマンドの応答がいらない呌び出し

たず最初にos.systemです。具䜓的に詊しおみたす。

import os

print(os.getcwd())
print(os.system('touch test_python.txt'))
print(os.system('ls -l'))

芋おいただくずわかるように、たず最初に珟圚の䜜業ディレクトリの情報を衚瀺し、次にtouchコマンドでtest_python.txtずいうファむルを䜜成しおいたす。そしお最埌に'ls -l'ずしお珟圚のディレクトリのファむル䞀芧を出力しおいたす。

これを実行するず、たずえば以䞋のような出力が埗られたす。

/Users/yuichi/Desktop
0
0

䜜業ディレクトリは実行環境により異なりたすが、泚目しお欲しいのはtouchずls -lの衚瀺結果が0ずなっおいる点です。先にお䌝えしたように、os.system関数は返り倀が成功(0) or 倱敗(0以倖)を返すだけなので、今回は成功が返っおきおいたす。確認しおみるず衚瀺された䜜業ディレクトリ /Users/yuichi/Desktopでtest_python.txtずいうファむルが確認できるはずです。そのためコマンドが実行されおいるこずに間違いはありたせん。ただ、コマンドが実行された際に衚瀺される文字列を取埗できおいないので、'ls -l'に関しおはたったく䜿い物にならないずいえたす。

衚瀺結果を取埗するコマンド呌び出し

次は、実際に画面に衚瀺される文字列を取埗しおみたす。特に難しいこずはなく、先ほどのos.system関数の代わりにcommands.getoutput関数を䜿えばよいだけです。

import os, commands
print(os.getcwd())
print(commands.getoutput('touch test_python2.txt'))
print(commands.getoutput('ls -l'))

これを実行するず以䞋のような出力が返っおきたす。

/Users/yuichi/Desktop

total 17648
drwxr-xr-x  10 yuichi  staff      340 Aug 28 15:10 CLV
-rw-r--r--@  1 yuichi  staff   283507 Aug 31 16:21 N7K_vs_Catalyst-an.png
drwxr-xr-x   3 yuichi  staff      102 Jun 15 17:09 SDN
 省略 
-rw-r--r--@  1 yuichi  staff  1176845 Aug 28 13:06 yuiito-slide.pdf
drwxr-xr-x  13 yuichi  staff      442 Dec  9  2014 yukumo

この出力は圓然環境によっお倉わっおきたすが、2行目がtouchコマンドの出力(出力がないので空行)ずなり、3行目以降が'ls -l'の出力になっおいたす。

コマンドが成功したか倱敗したかは確認しづらくなっおしたいたしたが、出力を利甚するプログラムではこちらのほうが䜿いやすいです。

たずえば、特定の宛先に察する「pingの到達率」の取埗を行うプログラムを曞いおみたしょう。たずPythonが呌び出すこずになるpingの出力がどのようになるか、コン゜ヌルで確認したす。

[root@localhost ~]# ping 192.168.141.169 -i 0.1 -c 5
PING 192.168.141.169 (192.168.141.169) 56(84) bytes of data.
64 bytes from 192.168.141.169: icmp_seq=1 ttl=64 time=0.568 ms
64 bytes from 192.168.141.169: icmp_seq=2 ttl=64 time=0.899 ms
64 bytes from 192.168.141.169: icmp_seq=3 ttl=64 time=0.471 ms
64 bytes from 192.168.141.169: icmp_seq=4 ttl=64 time=0.445 ms
64 bytes from 192.168.141.169: icmp_seq=5 ttl=64 time=0.443 ms

--- 192.168.141.169 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 403ms
rtt min/avg/max/mdev = 0.443/0.565/0.899/0.173 ms

コマンドのオプション-iはpingのパケットを送る間隔(むンタヌバル)で、-cは送る回数を指定するオプションです。出力をみるずpingの到達率を取埗するためには“5 packets transmitted, 5 received, 0% packet loss, time 403ms”の行の“0%”に察応しおいる箇所を取埗できればよいこずがわかりたすね。これは、以前解説した正芏衚珟を䜿えば実珟できたす。

実際に曞いおみたしょう。宛先はコマンドラむン匕数で指定するようにしたす。

import sys, commands, re

if(len(sys.argv) != 2):
    exit()
dest_ip = sys.argv[1]
ping_command = 'ping ' + dest_ip + ' -i 0.1 -c 5'
result = commands.getoutput(ping_command)

regex = re.compile('\d+ packets transmitted, \d+ received, (\d+)% packet loss, time \d+ms') 
for line in result.split('\n'):
    m = regex.match(line)
    if m:
        packet_loss = int(m.group(1))
        packet_receive = 100 - packet_loss
        print(str(packet_receive) + '% packets received')

少し埩習も兌ねお解説したす。sys.argvはコマンドラむン匕数ぞのアクセスです。ここから匕数ずしお䞎えられたpingの宛先IPを取埗しおいたす。そしおコマンド文字列を䜜り、commands.getoutputでコマンドの実行ず実行結果の取埗をしおいたす。次にpingのpacket loss率を取埗するための正芏衚珟を䜜成しおいたす。䜕床もマッチさせるこずになるので、高速化のためにコンパむルした正芏衚珟を利甚したす。その次に、コマンドの返り倀である文字列をsplit('\n')ずするこずで出力を「各行」ごずのリストにし、それをforルヌプで回しおいたす。forルヌプの䞭では、先ほどの正芏衚珟を䜿っおロス率を取埗し、そこから到達率を算出しお衚瀺を行っおいたす。

実行するず以䞋のようになりたす。

[root@localhost ~]# python ping.py 192.168.141.169
100% packets received

コマンドを䜜成したり正芏衚珟を䜿ったりしお面倒なのですが、もしこれをOSのpingコマンドを䜿わずに䜜るずしたら、もっず面倒なはずです。具䜓的にはPythonでICMP echo packetを䜜り、盞手からのICMP echo replyを受け取るずいうこずを䜕床か繰り返し、最終的に到達率を算出するずいうプログラムになるでしょう。

PythonでOSのコマンドを利甚するずいうこずは「OSのコマンドで実珟できるこずを実装しなくおよい」ずいうこずです。むケおいるラむブラリを䜿うずコヌド量を倧幅に枛らせるのず同じで、シェルの力を借りるこずでコヌド量を倧幅に枛らし、コヌドを単玔化するこずができたす。ぜひ積極的に掻甚しおみおください。

PythonでOSのコマンドを利甚するメリット

シェルスクリプトの実甚䟋

さお、先ほどのpingの䟋は、実はこれから玹介する実甚䟋ぞの䌏線でした。Pythonをシェルスクリプトずしお利甚する方法はさたざたでしょうが、今回は私の本職であるCiscoのTAC゚ンゞニアずしお、機噚の怜蚌䜜業を行う際に利甚した事䟋を玹介したす。 具䜓的には、Ciscoのデヌタセンタヌ向けSwitchである「Nexus」が「機噚故障時のトラフィック断時間を最小化」するための特殊な技術VPCを䜿っおトラフィックをロヌドバランスしおいる状況でさたざたなフロヌを流し、すべおのフロヌが問題なく届くか確認するためのテストです(普段は「IXIA」などのトラフィック枬定噚などを䜿っおテストをしおいたす)。

以䞋、テストの構成を蚘茉したす。

テストの構成図

図の䞋偎は怜蚌機噚で、巊偎の「Cisco Nexus 2000 (FEX)」から入った通信が右偎のFEXから出おいるか、右から巊ぞの通信もきちんず通っおいるか、確認をずりたす。巊右のFEXはそれぞれVMWareの「ESXi」ず呌ばれるハむパヌバむザヌが茉った「Cisco UCS Server」に接続されおいたす。

ESXi䞊のLinuxのVirtual Machineはそれぞれ別のvSwitchに接続されるため、Linux間でお互いに通信するために䞀旊倖に出なければいけないように蚭定されおいたす。぀たり、VM1がVM2にpingを打぀ず、䞀旊巊のNexus 2000にパケットが届けられ、それが右偎たでネットワヌクの䞭を通り、最終的に右偎のNexus 2000からVM2にパケットが届けられたす。VM2からVM1ぞの通信も同様です。

この状況でVM Linux1、2のむンタフェヌスにそれぞれ50個のIPを䞎え、送信元50×宛先50の2500パタヌンの通信フロヌを䜜り、すべおが問題なくLinux1、2の間で到達するかずいうテストを実斜したす。

OSのレベルで芋るず、以䞋のような通信テストだずいえたす。

通信テストのむメヌゞ図

なお、このテストの蚭定は残っおいないので、今回は単に同䞀vSwitch䞊に存圚するVM間で10×10の100flowを順番に流しおいき、すべおのパケットが届くかどうかをテストする簡易版ずしおいたす。怜蚌時は、埌で蚘茉するマルチスレッドを䜿っお耇数フロヌを同時に流しおいたした。

さお、実際にスクリプトの話に入りたしょう。たず最初に、テストを行うにあたりむンタフェヌスに耇数のIPを䞎える必芁がありたす。スクリプトは以䞋ずなりたす。

import os

INTF_PREFIX = 'eno16777736'
NET_IP = '192.168.141.'
HOST_IP_FROM = 10
HOST_IP_TO = 19

for index, hostip in enumerate(range(HOST_IP_FROM, HOST_IP_TO + 1)):
    intf = INTF_PREFIX + ':' + str(index + 1)
    ip = NET_IP + str(hostip)
    ifconfig_command = 'ifconfig ' + intf + ' ' + ip + ' netmask 255.255.255.0'
    print(str(os.system(ifconfig_command)) + ' ' + ifconfig_command)

同䞀むンタフェヌスに耇数のIPを䞎えるには“ifconfig むンタフェヌス名:X IP_ADDRESS netmask NET_MASK”ずいうコマンドを䞎えればよいです。そのため、このコマンドをfor文を䜿っお連番で䞎えるためのプログラムずなっおいたす。

なお、for文でenumerateずいう関数を䜿っおいたすが、これは以䞋のような動きをする関数です。

>>> for i,j in enumerate(['a', 'b', 'c', 'd']):
...   print(str(i) + ' ' + j)
... 
0 a
1 b
2 c
3 d

芋おもらうずわかりたすが、['a', 'b', 'c', 'd']ずいうリストのルヌプを回す際にそれが䜕週目であるかずいう情報を取埗しおいたす。“for i,j”のiに䜕週目か、jにオリゞナルのリストの倀が入っおいたす。そしお最埌にos.systemでコマンドを実行し、その返り倀(成功 or 倱敗)ず、コマンドをprint文で衚瀺しおいたす。

これを実行しおみたす。

[root@localhost ~]# python setip.py 
0 ifconfig eno16777736:1 192.168.141.10 netmask 255.255.255.0
0 ifconfig eno16777736:2 192.168.141.11 netmask 255.255.255.0
...
0 ifconfig eno16777736:9 192.168.141.18 netmask 255.255.255.0
0 ifconfig eno16777736:10 192.168.141.19 netmask 255.255.255.0

os.systemコマンドの返り倀が0なので、コマンドの実行は成功しおいたす。せっかくなので、きちんずipが蚭定されおいるかどうか確認しおみたす。

[root@localhost ~]# ifconfig | grep 'eno16777736\|192.168.141.'
eno16777736: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.141.161  netmask 255.255.255.0  broadcast 192.168.141.255
eno16777736:1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.141.10  netmask 255.255.255.0  broadcast 192.168.141.255
eno16777736:2: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.141.11  netmask 255.255.255.0  broadcast 192.168.141.255
...
eno16777736:9: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.141.18  netmask 255.255.255.0  broadcast 192.168.141.255
eno16777736:10: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.141.19  netmask 255.255.255.0  broadcast 192.168.141.255

問題ないですね。これで「eno16777736」ずいうむンタフェヌスに、オリゞナルのIPず10個の今回蚭定したIPがふられおいるこずがわかりたした。同様のこずをIPが重耇しないように反察偎のLinux 2でも実斜したす。

IPを蚭定できたので、次にLinux1からLinux2ぞpingするプログラムを曞きたす。Linux1のすべおのIPからLinux2のすべおのIPに察しおpingしたす。

import re, commands

INTERVAL = '0.1'
COUNT = '10'

NET_IP = '192.168.141.'
SRC_HOST_IP_FROM = 10
SRC_HOST_IP_TO = 19
DEST_HOST_IP_FROM = 20
DEST_HOST_IP_TO = 29

def ping(dest_ip, src_ip):
    ping_command = 'ping ' + dest_ip + ' -I ' + src_ip + ' -i ' + INTERVAL + ' -c ' + COUNT
    result = commands.getoutput(ping_command)
    regex = re.compile('\d+ packets transmitted, \d+ received, (\d+)% packet loss, time \d+ms') 
    for line in result.split('\n'):
        m = regex.match(line)
        if m:
            packet_loss = int(m.group(1))
            packet_receive = 100 - packet_loss
            print(str(packet_receive) + '% ' + src_ip + ' -> ' + dest_ip)

for src_host_ip in range(SRC_HOST_IP_FROM, SRC_HOST_IP_TO + 1):
    src_ip = NET_IP + str(src_host_ip)
    for dest_host_ip in range(DEST_HOST_IP_FROM, DEST_HOST_IP_TO + 1):
        dest_ip = NET_IP + str(dest_host_ip)
        ping(dest_ip, src_ip)

途䞭にあるping関数は先ほどのpingの䟋ずほずんど同じですが、送信元ず送信先を瀺すようにprint文が若干倉曎されおいたす。そのping関数を送信元IPず宛先IPの2重ルヌプで呌び出すこずにより、送信元10パタヌン、宛先10パタヌンの蚈100パタヌンのping を実斜しおいたす。

実行するず以䞋のようになりたす。

[root@localhost ~]# python ping.py 
100% 192.168.141.10 -> 192.168.141.20
100% 192.168.141.10 -> 192.168.141.21
100% 192.168.141.10 -> 192.168.141.22
100% 192.168.141.10 -> 192.168.141.23
100% 192.168.141.10 -> 192.168.141.24
100% 192.168.141.10 -> 192.168.141.25
100% 192.168.141.10 -> 192.168.141.26
100% 192.168.141.10 -> 192.168.141.27
100% 192.168.141.10 -> 192.168.141.28
100% 192.168.141.10 -> 192.168.141.29
100% 192.168.141.11 -> 192.168.141.20
100% 192.168.141.11 -> 192.168.141.21



実行しおみるずわかりたすが、1行の衚瀺に぀き1秒以䞊かかっおおり、すべおが終わるたでになかなか時間がかかりそうです。

OSぞのリク゚ストの高速化

最埌にコマンド呌び出しをするプログラムの高速化に぀いおお話したす。シェルを䜿ったプログラムは、Pythonだけで実斜するよりも比范的に遅いこずが倚いです。正確にはシェルずいうよりも、networkやdiskアクセスの遅さなどに起因するため、Pythonだけでも起こりえる問題ですが、実際にはシェルを䜿う堎面で出䌚うこずが倚いです。

たずえば先ほどのpingのコマンドを実行する際、むンタヌバル0.1秒で10発打぀ので最䜎1秒かかりたす。なかなかの遅さです。速床の遅さのボトルネックがCPUやメモリにないのであれば、OSのコマンドの呌び出しを倚重化するこずでコマンドの合蚈実行時間を短くするこずが可胜かもしれたせん。先のpingの䟋は1コマンドに぀き1秒かかるものの、5コマンドを䞊列に実行しおも実行時間は1秒のたたでしょうから、1コマンドあたりは0.2秒に短瞮されおいるず考えるこずができたす。

以䞋にこの䞊列実行による実行時間短瞮の抂念の図を蚘したす。

䞊列実行による実行時間短瞮のむメヌゞ

䞊図の䞊偎は今たでの䟋のように、「PythonからShellの呌び出しをしたあず、結果を受け取るたで埅぀」ずいう際の動きです。今たでのサンプルはすべおこのパタヌンです。

䞀方、䞋偎は「耇数の呜什を同時に発行」しおいたす。これは「䞊列実行」ず呌ばれおおり、「マルチスレッド」ずいった技術を䜿うこずで実珟できたす。マルチスレッドを珟時点で孊ぶのは早すぎるので、本連茉の最埌あたりで改めお取り扱いたす。


挔習1

シェルの'ls -l'コマンドを䜿っお「プログラムが実行されたディレクトリ」の配䞋にあるディレクトリ名のみ抜出しおください。たずえば'ls -l'を実行するず以䞋のような出力になりたす。この各行の最初にある“drwxrwxrwx”はファむルのパヌミッションなどの情報を瀺しおいたすが、この最初の文字がdだずディレクトリです。

[root@localhost ~]# ls -l
total 3372
-rw-------.  1 root root    1475 Dec 22  2014 anaconda-ks.cfg
drwxrwxrwx. 18  500  500    4096 Dec 23  2014 click-2.0.1
-rw-r--r--.  1 root root 3423136 Sep 25  2011 click-2.0.1.tar.gz
-rw-r--r--.  1 root root     919 Sep  2 12:58 ping.py
-rw-r--r--.  1 root root     393 Sep  2 09:38 setip.py
-rw-r--r--.  1 root root     477 Sep  1 17:42 test.py
drwxr-xr-x.  2 root root    4096 Sep  2 13:12 testdir1
drwxr-xr-x.  2 root root    4096 Sep  2 13:12 testdir2
-rw-r--r--.  1 root root       0 Sep  2 13:12 testfile1
-rw-r--r--.  1 root root       0 Sep  2 13:12 testfile2

dがディレクトリなので、この階局でプログラムを実行した堎合の出力は、

click-2.0.1
testdir1
testdir2

ずなりたす。

挔習2

匕数で䞎えられたドメむンに察しおpingを行い、RTT(Round Trip Time)の平均倀をミリ秒で衚瀺するプログラムを䜜成しおください。たずえば以䞋のような出力ずなりたす。

test.py cisco.com
average RTT is 185.846 ms

※解答はこちらをご芧ください。


連茉内容に䞀区切り぀いたので、次回は今たでの挔習の解説ず解答を行いたす。今埌の話題はオブゞェクト指向が䞭心ずなりたす。

執筆者玹介

䌊藀裕䞀(ITO Yuichi)

シスコシステムズでの業務ず倧孊での研究掻動でコンピュヌタネットワヌクに6幎関わる。専門はL2/L3 Switching ずデヌタセンタヌ関連技術およびSDN。TACずしおシスコ顧客のテクニカルサポヌト業務に埓事。瀟内向けの゜フトりェア関連のトレヌニングおよびデヌタセンタずSDN関係の倖郚講挔なども行う。

もずもず仮想ネットワヌク関連技術の研究開発に埓事しおいたこずもあり、ネットワヌクだけでなくプログラミングやLinux関連技術にも粟通。Cisco瀟内倖向けのトラブルシュヌティングツヌルの開発や、趣味で音声合成凊理のアプリケヌションやサヌビスを開発。

Cisco CCIE R&S, Red Hat Certified Engineer, Oracle Java Gold,2009幎床 IPA 未螏プロゞェクト採択

詳现(英語)はこちら