hana_shinのLinux技術ブログ

Linuxの技術情報を掲載しています。特にネットワークをメインに掲載していきます。

sarコマンドの%stealの意味について

1 はじめに

1.1 概要

sarコマンドやvmstatコマンドを使用すると、「%steal」という項目が表示されます。この項目は、仮想マシンのvCPUがホストの物理CPUに割り当てられない割合を表しています。この記事では、意図的にvCPUが物理CPUに割り当てられない状況を作り出し、「%steal」がどのように変化するかを検証してみます。

1.2 出力例

sarコマンドを実行すると、右から2列目に「%steal」が表示されていることがわかります。なお、sarコマンドの使い方は、sarコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# sar 1 -P ALL
19時22分50秒	CPU	%user	%nice	%system	%iowait	%steal	%idle
19時22分51秒	all	0.00	0.00	0.00	0.00	0.00	100.00
19時22分51秒	0	0.00	0.00	0.00	0.00	0.00	100.00
-snip-

vmstatコマンドを実行すると、右端に「st」(%steal)が表示されていることがわかります。なお、vmstatコマンドの使い方は、vmstatコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# vmstat 1 -w
   r    b         swpd         free         buff        cache   si   so    bi    bo   in   cs  us  sy  id  wa  st
   0    0            0      3368808         1636       185800    0    0    47     2   44   32   0   1  97   2   0
-snip-

1.3 検証方針

「%steal」の変化を確認するため、以下の手順を実行します。
1. 仮想マシンのvCPU(X)を物理CPU(Y)に固定的に割り当てる。
2. vCPU(X)で実行するプロセスより優先度の高いプロセスを物理CPU(Y)で実行して、vCPU(X)で実行するプロセスの時間を少なくする。

2 検証環境

2.1 版数の確認

仮想マシンのAlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

仮想マシンカーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

2.2 搭載CPU数の確認

仮想マシンはCPUを4個搭載しています。なお、lscpuコマンドの使い方は、lscpuコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# lscpu -xe
CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE
  0    0      0    0 0:0:0:0          yes
  1    0      1    1 1:1:1:1          yes
  2    0      2    2 2:2:2:2          yes
  3    0      3    3 3:3:3:3          yes

kvmホストはCPUを8個搭載しています。

[root@kvm ~]# lscpu -xe
CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE
  0    0      0    0 0:0:0:0          yes
  1    0      1    1 2:2:2:2          yes
  2    0      2    2 4:4:4:4          yes
  3    0      3    3 6:6:6:6          yes
  4    0      4    4 8:8:8:8          yes
  5    0      5    5 10:10:10:10      yes
  6    0      6    6 12:12:12:12      yes
  7    0      7    7 14:14:14:14      yes

3 実験1

以下の方針で検証環境を作成します。
1.アフィニティ
・vCPU0を物理CPU6に固定的に割り当てる。vCPUと物理CPUを固定的に割り当てることができれば、どのように割り当ててもかまいません。
2.CPU負荷
・物理CPU6でCPU使用率90%のリアルタイムプロセス(優先度99)を実行する。
・vCPU0でCPU使用率90%のタイムシェアリングプロセス(優先度120)を実行する。

3.1 事前準備

virsh vcpuinfoコマンドを使用すると、vCPU0が物理CPU6に割り当てられていることがわかります。しかし、CPUアフィニティが物理CPU0から物理CPU7に設定されているので、物理CPUの負荷状況によっては、vCPU0が物理CPU6以外に割り当てられる可能性があります。

[root@kvm ~]# virsh vcpuinfo --pretty 01_server_alma92
VCPU:           0
CPU:            6
状態:         実行中
CPU 時間:     33.6s
CPU アフィニティー: 0-7/8
-snip

そこで、vCPU0を物理CPU6に固定的に割り当てるため、virsh vcpupinコマンドを使用してvCPU0を物理CPU6に固定します。

[root@kvm ~]# virsh vcpupin 01_server_alma92 --vcpu 0 6

仮想マシンを再起動します。

[root@kvm ~]# virsh reboot 01_server_alma92
ドメイン '01_server_alma92' は再起動されています

virsh vcpuinfoコマンドを実行すると、CPU アフィニティーが物理CPU6となっているので、vCPU0が物理CPU6に固定的に割り当てられていることがわかります。

[root@kvm ~]# virsh vcpuinfo --pretty 01_server_alma92
VCPU:           0
CPU:            6
状態:         実行中
CPU 時間:     58.4s
CPU アフィニティー: 6/8
-snip-

3.2 動作検証(kvmホストのCPUに負荷をかける)

stress-ngコマンドを以下の条件で実行します。なお、stress-ngコマンドの使い方は、stress-ngコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。
・物理CPU6でCPU使用率90%のstress-ngプロセスを実行する。
・stress-ngプロセスは、ラウンドロビン(RR)のスケジューリングポリシーでリアルタイムプロセスとして実行する。

[root@kvm ~]# stress-ng -k -l 90 -c 1 --taskset 6 --sched rr --sched-prio 99 -q &

psコマンドでstress-ngプロセスの状態を確認します。物理CPU6でCPU使用率90%程度で動作していることがわかります。また。ラウンドロビン(RR)のスケジューリングポリシーで優先度99のリアルタイムプロセスとして動作していることがわかります。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@kvm ~]# ps -C stress-ng -o comm,pid,ppid,psr,%cpu,cls,ni,rtprio
COMMAND             PID    PPID PSR %CPU CLS  NI RTPRIO
stress-ng          2476    2209   6  0.0  RR   -     99
stress-ng          2477    2476   6 89.0  RR   -     99

仮想マシンでsarマンドを実行すると、CPU全体(all)の「%steal」が約7%、vCPU0の「%steal」が22%であることがわかります。

[root@server ~]# sar -P ALL 1
22時01分16秒     CPU     %user     %nice   %system   %iowait    %steal     %idle
22時01分17秒     all      0.00      0.00      0.00      0.00      6.76     93.24
22時01分17秒       0      0.00      0.00      0.00      0.00     22.48     77.52
22時01分17秒       1      0.00      0.00      0.00      0.00      0.00    100.00
22時01分17秒       2      0.00      0.00      0.00      0.00      0.00    100.00
22時01分17秒       3      0.00      0.00      0.00      0.00      0.00    100.00
-snip-

さらに、ターミナルを開いてvmstatコマンドを実行してみます。sarコマンドとvmstaコマンドを同時に実行して結果を見比べると、vmstatコマンドの「st」列はsarコマンドのCPU全体の「%steal」に相当することがわかります。

[root@server ~]# vmstat 1 -w
--procs-- -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu--------
   r    b         swpd         free         buff        cache   si   so    bi    bo   in   cs  us  sy  id  wa  st
   2    0            0      3379184         1636       185656    0    0    21     1   23   22   0   0  94   0   5
   0    0            0      3379184         1636       185656    0    0     0     0   73   92   0   0  93   0   7
   0    0            0      3379184         1636       185656    0    0     0     0   56   75   0   0  89   0  11
-snip-

3.3 動作検証(仮想マシンのCPUに負荷をかける)

さらに、kvmホストでもstress-ngプロセスを実行します。kvmホストで優先度の高いstress-ngプロセスを実行すると、仮想マシンで実行するstress-ngプロセスが実行されにくくなります。

[root@server ~]# stress-ng -k -l 90 -c 1 --taskset 0 -q &

psコマンドを使用して、stress-ngプロセスの状態を確認します。stress-ngプロセスをCPU使用率90%で起動していますが、仮想マシンで実行しているプロセスよりも優先度の高いプロセスがkvmホストで実行されているので、vCPU0のCPU使用率が約7%であることが確認できます。また、このプロセスがタイムシェアリングプロセス(TS)として実行されていることも確認できます。

[root@server ~]# ps -C stress-ng -o comm,pid,ppid,psr,%cpu,cls,ni,rtprio
COMMAND             PID    PPID PSR %CPU CLS  NI RTPRIO
stress-ng          1303    1271   0  0.0  TS   0      -
stress-ng          1304    1303   0  7.4  TS   0      -

sarコマンドを実行します。vCPUでstress-ngプロセスを実行すると、vCPUが物理CPUに割り当てられる割合が少なくなるので、「%steal」の値が大きくなることがわかります。

[root@server ~]# sar -P ALL 1
平均値:      CPU     %user     %nice   %system   %iowait    %steal     %idle
平均値:      all      2.26      0.00      0.25      0.00     22.56     74.94
平均値:        0      9.15      0.00      0.34      0.00     90.51      0.00
平均値:        1      0.00      0.00      0.33      0.00      0.00     99.67
平均値:        2      0.00      0.00      0.33      0.00      0.66     99.01
平均値:        3      0.00      0.00      0.00      0.00      0.33     99.67

vmstat コマンドを実行します。「st」列を確認すると、22%程度(全vCPUの25%)になっていることがわかります。

[root@server ~]# vmstat 1 -w
--procs-- -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu--------
   r    b         swpd         free         buff        cache   si   so    bi    bo   in   cs  us  sy  id  wa  st
   1    0            0      3330928         1636       208972    0    0     9     0   30   20   1   0  86   0  13
   1    0            0      3330928         1636       208972    0    0     0     0  287   95   2   0  76   0  22
   1    0            0      3330928         1636       208972    0    0     0     0  151   68   2   0  75   0  22
   1    0            0      3330928         1636       208972    0    0     0     0  337  138   2   0  76   0  22

4 実験2

以下の方針で検証環境を作成します。
1. アフィニティ
・vCPU0を物理CPU6に固定的に割り当てる。
・vCPU1を物理CPU7に固定的に割り当てる。
2. 物理CPUの負荷
・物理CPU6でCPU使用率90%のリアルタイムプロセス(優先度99)を実行する。
・物理CPU7でCPU使用率90%のリアルタイムプロセス(優先度99)を実行する。
3. 仮想CPUの負荷
・vCPU0でCPU使用率90%のタイムシェアリングプロセス(優先度120)を実行する。
・vCPU1でCPU使用率90%のタイムシェアリングプロセス(優先度120)を実行する。

仮想マシンでstress-ngコマンドを実行します。

[root@server ~]# stress-ng -k -l 90 -c 1 --taskset 0 -q &
[root@server ~]# stress-ng -k -l 90 -c 1 --taskset 1 -q &

kvmホストでstress-ngコマンドを実行します。

[root@kvm ~]# stress-ng -k -l 90 -c 1 --taskset 6 --sched rr --sched-prio 99 -q &
[root@kvm ~]# stress-ng -k -l 90 -c 1 --taskset 7 --sched rr --sched-prio 99 -q &

sarコマンドを実行すると、vCPU0とvCPU1の「%steal 」の値が約90%となっていて、vCPU0とvCPU1は物理CPUにほとんど割り当てられていないことがわかります。また、allの「%steal 」は約45%になっていて、仮想マシンのvCPUの約半分が物理CPUに割り当てられていないことがわかります。

[root@server ~]# sar -P ALL 1
Linux 5.14.0-284.11.1.el9_2.x86_64 (server)     2023年12月02日  _x86_64_        (4 CPU)

20時50分55秒     CPU     %user     %nice   %system   %iowait    %steal     %idle
20時50分56秒     all      4.31      0.00      0.25      0.00     44.92     50.51
20時50分56秒       0      8.25      0.00      0.00      0.00     91.75      0.00
20時50分56秒       1      9.28      0.00      0.00      0.00     90.72      0.00
20時50分56秒       2      0.00      0.00      0.99      0.00      0.00     99.01
20時50分56秒       3      0.00      0.00      0.00      0.00      0.00    100.00

vmstatコマンドを実行します。「st」列の値が約45%になっていて、仮想マシンのvCPUの約半分が物理CPUに割り当てられていないことがわかります。

[root@server ~]# vmstat 1 -w
--procs-- -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu--------
   r    b         swpd         free         buff        cache   si   so    bi    bo   in   cs  us  sy  id  wa  st
   2    0            0      3361036         1636       193308    0    0    23     1   28   25   0   1  94   0   6
   3    0            0      3361036         1636       193308    0    0     0     0  527  194   4   0  51   0  44
   2    0            0      3361036         1636       193308    0    0     0     0  360  137   5   1  50   0  45
   2    0            0      3361036         1636       193308    0    0     0     0  395  153   5   0  50   0  45
-snip-

5 まとめ

・「%steal」項目は、vCPUが物理CPUに割り当てられなかった割合を表しています。
・vCPUでプロセスを実行していない場合より、vCPUでプロセスを実行している方が、「%steal」の値は大きくなります。

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

gpgコマンドの使い方

1 gpgコマンドとは?

gpgコマンドは、ファイルの暗号化・復号や署名の作成・検証等を行うコマンドです。

2 検証環境

2.1 ネットワーク構成

gpgコマンドの動作確認をするため、2台の仮想マシン(server ,client)を使用します。そして、X-sanがserver、Y-sanがclientで、それぞれ鍵ペア(公開鍵と秘密鍵)を作成します。

                 192.168.122.0/24
server ------------------------------------- client
      .14                                .87

本記事では、以下のことを確認してみます。
・ファイルの暗号化・復号
・ファイルに署名を作成・署名の検証

2.2 版数

仮想マシンのAlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

仮想マシンカーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

私の環境ではpinentryパッケージのインストールが必要だったので、仮想マシンにpinentryパッケージをインストールしました。

[root@server ~]# dnf install pinentry

gpgの版数は以下のとおりです。

[root@server ~]# gpg --version
gpg (GnuPG) 2.3.3
-snip

3 オプション一覧

gpgコマンドのオプションは以下のとおりです。

[root@server ~]# gpg --help
gpg (GnuPG) 2.3.3
libgcrypt 1.10.0-unknown
Copyright (C) 2021 Free Software Foundation, Inc.
License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /root/.gnupg
サポートしているアルゴリズム:
公開鍵: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
暗号方式: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256,
      TWOFISH, CAMELLIA128, CAMELLIA192, CAMELLIA256
AEAD: EAX, OCB
ハッシュ: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
圧縮: 無圧縮, ZIP, ZLIB, BZIP2

形式: gpg [オプション] [ファイル]
署名、検査、暗号化または復号
デフォルトの操作は、入力データに依存

コマンド:

 -s, --sign                         署名を作成
     --clear-sign                   クリア・テクスト署名を作成
 -b, --detach-sign                  分遣署名を作成
 -e, --encrypt                      データを暗号化
 -c, --symmetric                    暗号化には共通鍵暗号方式のみを使用
 -d, --decrypt                      データを復号 (デフォルト)
     --verify                       署名を検証
 -k, --list-keys                    鍵の一覧
     --list-signatures              鍵と署名の一覧
     --check-signatures             鍵署名の検査と一覧
     --fingerprint                  鍵とフィンガープリントの一覧
 -K, --list-secret-keys             秘密鍵の一覧
     --generate-key                 新しい鍵ペアを生成
     --quick-generate-key           すばやく新しい鍵ペアを生成
     --quick-add-uid                すばやく新しいユーザIDを追加
     --quick-revoke-uid             すばやくユーザIDを失効
     --quick-set-expire             すばやく新しい有効期限を設定
     --full-generate-key            全機能の鍵ペアを生成
     --generate-revocation          失効証明書を生成
     --delete-keys                  公開鍵リングから鍵を削除
     --delete-secret-keys           秘密鍵リングから鍵を削除
     --quick-sign-key               すばやく鍵に署名
     --quick-lsign-key              すばやく鍵へローカルに署名
     --quick-revoke-sig             すばやく鍵への署名を失効
     --sign-key                     鍵に署名
     --lsign-key                    鍵へローカルに署名
     --edit-key                     鍵への署名や編集
     --change-passphrase            パスフレーズの変更
     --export                       鍵をエクスポートする
     --send-keys                    鍵サーバに鍵をエクスポートする
     --receive-keys                 鍵サーバから鍵をインポートする
     --search-keys                  鍵サーバの鍵を検索する
     --refresh-keys                 鍵サーバから鍵を全部更新する
     --import                       鍵のインポート/マージ
     --card-status                  カード・ステイタスを表示
     --edit-card                    カードのデータを変更
     --change-pin                   カードのPINを変更
     --update-trustdb               信用データベースを更新
     --print-md                     メッセージ・ダイジェストを表示
     --server                       サーバ・モードで実行
     --tofu-policy VALUE            TOFUポリシーを鍵に設定する

診断出力を制御するオプション:
 -v, --verbose                      冗長
 -q, --quiet                        いくらかおとなしく
     --options FILE                 FILEからオプションを読み込みます
     --log-file FILE                FILEにサーバ・モードのログを書き出す

コンフィグレーションを制御するオプション:
     --default-key NAME             デフォルトの秘密鍵としてNAMEを用いる
     --encrypt-to NAME              ユーザID NAMEにも暗号化する
     --group SPEC                   電子メールエイリアスを設定する
     --openpgp                      厳密なOpenPGPの振舞を採用
 -n, --dry-run                      無変更
 -i, --interactive                  上書き前に確認

出力を制御するオプション:
 -a, --armor                        ASCII形式の外装を作成
 -o, --output FILE                  出力をFILEに書き出す
     --textmode                     正準テキスト・モードを使用
 -z N                               圧縮レベルをNに設定 (0は非圧縮)

鍵のインポートとエクスポートを制御するオプション:
     --auto-key-locate MECHANISMS   メールアドレスによって鍵を特定する際、MECHANISMSを使用する
     --auto-key-import              署名から手元にない鍵をインポートする
     --include-key-block            署名に公開鍵を含める
     --disable-dirmngr              dirmngrへのすべてのアクセスを無効とする

鍵を指定するオプション:
 -r, --recipient USER-ID            USER-ID用に暗号化
 -u, --local-user USER-ID           署名や復号にこのUSER-IDを使用

Project-Id-Version: gnupg 2.3.0
Report-Msgid-Bugs-To: translations@gnupg.org
PO-Revision-Date: 2021-04-08 10:15+0900
Last-Translator: NIIBE Yutaka <gniibe@fsij.org>
Language-Team: none
Language: ja
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Plural-Forms: nplurals=1; plural=0;
:

(コマンドとオプション全部の一覧は、マニュアル・ページをご覧ください)

例:

 -se -r Bob [ファイル]      ユーザBobへ署名と暗号化
 --clear-sign [ファイル]     クリア・テクスト署名を作成
 --detach-sign [ファイル]   分遣署名を作成
 --list-keys [名前]         鍵を表示
 --fingerprint [名前]       フィンガープリントを表示

バグは <https://bugs.gnupg.org> までご報告ください。

4 基本的な使い方

実践編の前に、gpgコマンドの基本的な使い方を確認してみます。

4.1 鍵ペアを作成する方法(--gen-key)

--gen-keyオプションは、公開鍵と秘密鍵の鍵ペアを作成するオプションです。

[root@server ~]# gpg --gen-key

「本名」はX-san、「電子メールアドレス」はX-san@hoge.comと入力します。そして、「O」を入力して「Enter」キーを押下します。

秘密鍵が盗まれても、悪用されにくくするため、秘密鍵パスフレーズを設定します。パスフレーズを設定したら、「Tab」キーを押下して「OK」にカーソルをあわせます。そして、「Enter」を押下します。

再度、パスフレーズを入力します。

鍵ペアの作成が完了すると、以下のようになります。

4.2 公開鍵を確認する方法( --list-keys、または-k(小文字))

--list-keysオプションは、公開鍵を表示するオプションです。4.1で作成した公開鍵を確認してみます。公開鍵のidがX-sanであることがわかります。

[root@server ~]# gpg --list-keys
/root/.gnupg/pubring.kbx
------------------------
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      DC4A5479C1E3417346F1AC5C084F72AF5B2AFB76
uid           [  究極  ] X-san <X-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

4.3 秘密鍵を確認する方法(--list-secret-keys、または-K(大文字))

--list-secret-keysオプションは、秘密鍵を表示するオプションです。4.1で作成した秘密鍵を確認してみます。

[root@server ~]# gpg --list-secret-keys
/root/.gnupg/pubring.kbx
------------------------
sec   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      DC4A5479C1E3417346F1AC5C084F72AF5B2AFB76
uid           [  究極  ] X-san <X-san@hoge.com>
ssb   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

4.4 秘密鍵を削除する方法(--delete-secret-keys)

--delete-secret-keysオプションは、秘密鍵を削除するオプションです。鍵の削除は秘密鍵、公開鍵の順で行わないと、gpgコマンドでエラーが出力されるので、まず、X-sanの秘密鍵から削除します。

[root@server ~]# gpg --delete-secret-keys X-san

「鍵を削除するか?」と聞かれるので、「y」を入力します。

「鍵を削除するか?」と聞かれるので、「Tab」キーを押下して、「鍵を削除する」にカーソルを合わせます。そして、「Enter」キーを押下します。

再度、「鍵を削除するか?」と聞かれるので、「Tab」キーを押下して、「鍵を削除する」にカーソルを合わせます。そして、「Enter」キーを押下します。


秘密鍵を確認すると、リストから削除されたことがわかります。

[root@server ~]# gpg --list-secret-keys
[root@server ~]#

4.5 公開鍵を削除する方法(--delete-keys)

--delete-keysオプションは、公開鍵を削除するオプションです。X-sanの公開鍵を削除します。

[root@server ~]# gpg --delete-keys X-san
gpg (GnuPG) 2.3.3; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

pub  rsa3072/084F72AF5B2AFB76 2023-11-27 X-san <X-san@hoge.com>

この鍵を鍵リングから削除しますか? (y/N) y

リストを確認すると、X-sanの公開鍵が削除されたことがわかります。

[root@server ~]# gpg --list-keys
gpg: 信用データベースの検査
gpg: 究極的に信用する鍵が見つかりません

4.6 鍵をエクスポートする方法(--export)

4.4, 4.5でX-sanの公開鍵、秘密鍵を削除したので、4.1の手順にしたがって、X-sanの公開鍵、秘密鍵を作成しておきます。

--exportオプションは、公開鍵をファイルにエクスポート(書き出す)するオプションです。X-sanの公開鍵をエクスポートします。

[root@server ~]# gpg -ao X-san.pub --export X-san

公開鍵を確認すると、X-sanの公開鍵がファイルにエクスポートされたことがわかります。

[root@server ~]# ls X-san.pub
X-san.pub

4.7 鍵をインポートする方法(--import)

clientで作成した公開鍵をserverに送信します。そして、公開鍵のリストにインポートしてみます。

--importオプションは、公開鍵をリストに追加するオプションです。clientでY-sanの公開鍵を作成します。

[root@client ~]# gpg --gen-key

clientで作成したY-sanの公開鍵をエクスポートします。

[root@client ~]# gpg -ao Y-san.pub --export Y-san

serverに公開鍵を送信します。

[root@client ~]# scp Y-san.pub root@192.168.122.14:/root

serverでY-sanの公開鍵を確認します。

[root@server ~]# ls Y-san.pub
Y-san.pub

Y-sanの公開鍵をインポートします。

[root@server ~]# gpg --import Y-san.pub
gpg: 鍵F12039A2064B868E: 公開鍵"Y-san <Y-san@hoge.com>"をインポートしました
gpg:           処理数の合計: 1
gpg:             インポート: 1

公開鍵のリストを確認します。X-sanの公開鍵の他に、Y-sanの公開鍵もリストに追加されていることがわかります。

[root@server ~]# gpg --list-keys
/root/.gnupg/pubring.kbx
------------------------
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      D8AC6658FC22B64CAD46DC005098AB5CC2CA9B28
uid           [  究極  ] X-san <X-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      3F605381256675ADC76A2ECDF12039A2064B868E
uid           [  不明  ] Y-san <Y-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

特定の公開鍵だけを表示するには、--list-keysオプションのパラメータにuidを指定します。
たとえば、X-sanだけの公開鍵を表示するには、以下のように実行します。

[root@server ~]# gpg --list-keys X-san
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      D8AC6658FC22B64CAD46DC005098AB5CC2CA9B28
uid           [  究極  ] X-san <X-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

5 実践編(ファイルを暗号化・復号する方法)

以下の順でファイルを暗号化・復号する方法を確認してみます。
1. serverでY-sanの公開鍵でファイルを暗号化する
2. 暗号化したファイルをserverからclientに送信する
3. clientでY-sanの秘密鍵でファイルを復号する

5.1 事前準備

clientで鍵ペアを作成します。作成した公開鍵を確認します。

[root@client ~]# gpg --list-keys
/root/.gnupg/pubring.kbx
------------------------
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      3F605381256675ADC76A2ECDF12039A2064B868E
uid           [  究極  ] Y-san <Y-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

Y-sanの公開鍵をファイルにエクスポートします。

[root@client ~]# gpg -ao Y-san.pub --export Y-san

エクスポートしてファイルをserverに送信します。

[root@client ~]# scp Y-san.pub root@192.168.122.14:/root

エクスポートしたY-sanの公開鍵はいつでもリストから取り出すことができるので、削除します。

[root@client ~]# rm Y-san.pub
rm: 通常ファイル 'Y-san.pub' を削除しますか? y

Y-sanの公開鍵を確認します。

[root@server ~]# ls Y-san.pub
Y-san.pub

Y-sanの公開鍵をインポートしてリストに追加します。

[root@server ~]#  gpg --import Y-san.pub
gpg: 鍵F12039A2064B868E:"Y-san <Y-san@hoge.com>"変更なし
gpg:           処理数の合計: 1
gpg:               変更なし: 1

Y-sanの公開鍵をリストに追加したので、Y-sanの公開鍵を削除します。

[root@server ~]# rm Y-san.pub
rm: 通常ファイル 'Y-san.pub' を削除しますか? y

公開鍵を確認すると、X-sanの公開鍵の他に、Y-sanの公開鍵が追加されたことがわかります。

[root@server ~]# gpg --list-keys
/root/.gnupg/pubring.kbx
------------------------
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      D8AC6658FC22B64CAD46DC005098AB5CC2CA9B28
uid           [  究極  ] X-san <X-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      3F605381256675ADC76A2ECDF12039A2064B868E
uid           [  不明  ] Y-san <Y-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

5.2 ファイルを暗号化・復号する方法

serverでテスト用ファイルを作成します。

[root@server ~]# vi test.txt
[root@server ~]# cat test.txt
0123456789

Y-sanの公開鍵でテスト用ファイルを暗号化します。

[root@server ~]# gpg -r Y-san -ea test.txt
gpg: FBA0E21345620F47: この鍵が本当に本人のものである、という兆候が、ありません

sub  rsa3072/FBA0E21345620F47 2023-11-27 Y-san <Y-san@hoge.com>
  主鍵フィンガープリント: 3F60 5381 2566 75AD C76A  2ECD F120 39A2 064B 868E
  副鍵フィンガープリント: 65FF F682 24BA B68B CF42  3169 FBA0 E213 4562 0F47

この鍵は、このユーザIDをなのる本人のものかどうか確信でき
ません。今から行うことを*本当に*理解していない場合には、
次の質問にはnoと答えてください。

それでもこの鍵を使いますか? (y/N) y

暗号化したファイルを確認します。

[root@server ~]# cat test.txt.asc
-----BEGIN PGP MESSAGE-----

hQGMA/ug4hNFYg9HAQwAo4TLQiL3dvIucsupKIHZ+PxUrrg4fSxErG0R16ZMtB5R
C/vWll1bgPIiHaDpSgvx0iFNdbJN6PXzDsxqIxBy3LaasdmjamYcPIUF0PvXzWmE
VOH2ZMPJtNDQsySQ78oLov/iiRdGX5WjdF7vWm05HxS1JrYaKSblke9FpPJMii39
NiqRA+zKACIjOYrNZVYXDQGk0kiO8OvIAO2HJ2Qa4Y1XGk/T8Mv6aRfOxnqz6owN
ljG/hFDMilgAeiMT1VtUkwfCll9rKEGzjvAGniknkncBKhu+o/BlGA+1x9Oazfei
Dz6T/MGmslqow7xvInSFOdPFIBLPZEWEvu2+3XydECMMxrbXHL/VFVLAN0nuIzNE
H2oGweYQ0G+bBTpSKL2GYadNrXbV6oa6wEp+ZmjfljjAu9UEGHz+0TMUnNI4oXur
3NojbeUc4PC6my+XMICLZSpjvPA8Cg7iqwUoFdEBw29P+XYhK5wGtUu+kwwn2dqg
dqjsiUhZrP9HeGYNzchy1FgBCQIQdIrpYtGYNGCyzVy2AxZyi67PN9QogNlU0uuG
NP1isCnp581T4oLdU+pYyKAUmnq4mfarNx0SsjB/yq7uiccnW9csBrDHX10Hm82j
IZtqu2r0VByy
=YYzY
-----END PGP MESSAGE-----

暗号化したファイルをclientに送信します。

[root@server ~]# scp test.txt.asc root@192.168.122.87:/root

clientで暗号化したファイルを確認します。

[root@client ~]# ls test.txt.asc
test.txt.asc

暗号化したファイルをY-sanの秘密鍵で復号します。-oオプションを使用して、復号した結果をtest.txtに出力します。

[root@client ~]# gpg -d -o test.txt test.txt.asc
gpg: rsa3072鍵, ID FBA0E21345620F47, 日付2023-11-27に暗号化されました
      "Y-san <Y-san@hoge.com>

復号したファイルを確認します。正しく復号できていることがわかります。

[root@client ~]# cat test.txt
0123456789

6 実践編(署名を作成・検証する方法)

以下の順で署名の作成・検証する方法を確認してみます。
1. serverでX-sanの秘密鍵でファイルに署名する
2. 署名したファイルをserverからclientに送信する
3. clientでX-sanの公開鍵で署名を検証する

6.1 事前準備

serverでテスト用ファイルを作成します。

[root@server ~]# vi test.txt
[root@server ~]# cat test.txt
0123456789

X-sanの秘密鍵で署名したファイルをclientで検証するには、X-sanの公開鍵をclientに送信しておく必要がありますので、X-sanの公開鍵をファイルにエクスポートします。

[root@server ~]# gpg -ao X-san.pub --export X-san

エクスポートしたファイルをclientに送信します。

[root@server ~]# scp X-san.pub root@192.168.122.87:/root

X-sanの公開鍵を削除します。

[root@server ~]# rm X-san.pub
rm: 通常ファイル 'X-san.pub' を削除しますか? y

X-sanの公開鍵をインポートします。

[root@client ~]# gpg --import X-san.pub
gpg: 鍵5098AB5CC2CA9B28: 公開鍵"X-san <X-san@hoge.com>"をインポートしました
gpg:           処理数の合計: 1
gpg:             インポート: 1

X-sanの公開鍵をリストに追加したので、X-sanの公開鍵を削除しておきます。

[root@client ~]# rm X-san.pub
rm: 通常ファイル 'X-san.pub' を削除しますか? y

公開鍵のリストを確認します。X-sanの公開鍵がリストに追加されてことがわかります。

[root@client ~]# gpg --list-keys
/root/.gnupg/pubring.kbx
------------------------
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      3F605381256675ADC76A2ECDF12039A2064B868E
uid           [  究極  ] Y-san <Y-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      D8AC6658FC22B64CAD46DC005098AB5CC2CA9B28
uid           [  不明  ] X-san <X-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

X-sanの公開鍵を削除します。

[root@client ~]# rm X-san.pub
rm: 通常ファイル 'X-san.pub' を削除しますか? y

6.2 署名の作成・検証する方法

X-sanの秘密鍵でテスト用ファイルに署名をします。

[root@server ~]# gpg --clearsign test.txt

署名したファイルを確認します。

[root@server ~]# cat test.txt.asc
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

0123456789
-----BEGIN PGP SIGNATURE-----

iQGzBAEBCAAdFiEE2KxmWPwitkytRtwAUJirXMLKmygFAmVkdNQACgkQUJirXMLK
myi9rgv+OXcaDKzYzB1zMuLeeHiKp+8Cxvx7dNcA57Pso0awgiQlYdRZjJKrbPki
ymasPetS5Y9AQlFfY/Hd3BSw+rd4TI+IwjDBcoCxEI5OrEWvonSVWxD0cZnuy7Ux
1DlzR58xEwlWafA433EKk7i1FmWFeObTMhZjIA0z32d8Asu+XBNZtvDhbB1y4F+f
w26bZVSfYunf3TUICOWNOvvmBrH+RJSuZYuKUJ3Swm+3jcJUyj8OX05pmexqWRwC
5vjCyrVk/KOhm4WGawC+eT1HFiB7/AlUWHOuE/f6mKK0WLXF9Wnya4wlZHGyIAGk
nynAoJSVl0zA+LnHWDs941KN/W7mKNU58XgxfDAC8I4GX5BlzguETCMS3/K8s/dD
bpO8pMsVLPae+InEshDPbnGlRJeQjVhmfB0fz+ChfsdDLaJmC4DQb/Sl7/sHFtU9
FalEKweOJUA1b+rgr1iwYZk2hx8yavie76s3mFqvEbqQPjmd/HWr0c73AsU+kPuP
9tjRCpBG
=tynM
-----END PGP SIGNATURE-----

署名したファイルをclientに送信します。

[root@server ~]# scp test.txt.asc root@192.168.122.87:/root

署名を検証します。X-sanが署名したことがわかります。

[root@client ~]# gpg --verify test.txt.asc
gpg: 2023年11月27日 19時52分04秒 JSTに施された署名
gpg:                RSA鍵D8AC6658FC22B64CAD46DC005098AB5CC2CA9B28を使用
gpg: "X-san <X-san@hoge.com>"からの正しい署名 [不明の]
gpg: *警告*: この鍵は信用できる署名で証明されていません!
gpg:       この署名が所有者のものかどうかの検証手段がありません。
 主鍵フィンガープリント: D8AC 6658 FC22 B64C AD46  DC00 5098 AB5C C2CA 9B28
gpg: *警告*: 分遣署名ではありません。ファイル「test.txt」は検証され*ませんでした*!

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

GPGによる公開鍵暗号と署名

Matplotlibの使い方

1 Matplotlibとは?

Matplotlibはグラフを描画するためのライブラリです。

2 検証環境

AlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

pythonの版数は以下のとおりです。

[root@server ~]# python -V
Python 3.9.16

3 Matplotlibのインストール方法

グラフを描画するため、Matplotlibライブラリをインストールします。

[root@server ~]# pip install matplotlib

Matplotlibの版数を確認します。

[root@server ~]# pip show matplotlib
Name: matplotlib
Version: 3.8.0
Summary: Python plotting package
Home-page: https://matplotlib.org
Author: John D. Hunter, Michael Droettboom
Author-email: matplotlib-users@python.org
License: PSF
Location: /usr/local/lib64/python3.9/site-packages
Requires: contourpy, cycler, fonttools, importlib-resources, kiwisolver, numpy, packaging, pillow, pyparsing, python-dateutil
Required-by:

Matplotlibで描画するグラフに日本語のタイトル等を表示するため、japanize-matplotlibをインストールします

[root@server ~]# pip install japanize-matplotlib

japanize-matplotlibの版数を確認します。

[root@server ~]# pip show japanize-matplotlib
Name: japanize-matplotlib
Version: 1.1.3
Summary: matplotlibのフォント設定を自動で日本語化する
Home-page: https://github.com/uehara1414/japanize-matplotlib
Author: uehara1414
Author-email: akiya.noface@gmail.com
License: MIT License
Location: /usr/local/lib/python3.9/site-packages
Requires: matplotlib
Required-by:

4 DataFrameからグラフを作成する方法

4.1 グラフの種類

種類 概要
折れ線グラフ 変化をみるときに使う
積み上げグラフ 変化の要因を知りたいときに使う
面グラフ 変化の大きさを強調してみるときに使う
箱ひげグラフ データの散らばり具合を見るときに使う

4.2 テスト用ファイルの作成

テストプログラムが読み込む、テスト用ファイルを作成します。

[root@server ~]# cat test.csv
名前,国語,数学,英語
佐藤,90,50,80
鈴木,50,100,80
田中,70,60,80
伊藤,40,60,100

4.3 折れ線グラフを作成する方法(plot)

plotメソッドは、折れ線グラフを作成するためのメソッドです。

折れ線グラフを作成するテストプログラムを作成します。importステートメントの意味は以下のとおりです。
・matplotlibライブラリからグラフ描画(pyplot)に関する機能をpltとしてインポートする。
・日本語タイトルなどを描画するために使用するjapanize_matplotlibをインポートする。
・データ分析のために使用するpandasライブラリをpdとしてインポートする。
なお、pandasの使い方は、pandasの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# cat test.py
#!/usr/bin/python3
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 折れ線グラフを作成
df.plot()

# グラフを表示
plt.show()

テストプログラムを実行すると、テスト用ファイルのデータが折れ線グラフで表示されていることがわかります。なお、本記事では、グラフを表示するために、MobaXtermを使用しています。MobaXtermの使い方は、MobaXtermの使い方 - hana_shinのLinux技術ブログを参照してください。

4.4 棒グラフを作成する方法(plot.bar)

棒グラフを作成するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 棒グラフを作成
df.plot.bar()

# グラフを表示
plt.show()

テストプログラムを実行すると、棒グラフが表示されていることがわかります。

4.5 積み上げグラフを作成する方法(plot.bar(stacked=true))

積み上げグラフを作成するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 積み上げグラフを作成
df.plot.bar(stacked=True)

# グラフを表示
plt.show()

テストプログラムを実行すると、積み上げグラフが表示されていることがわかります。

4.6 面グラフを作成する方法(plot.area)

面グラフを作成するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 面グラフを作成
df.plot.area()

# グラフを表示
plt.show()

テストプログラムを実行すると、面グラフが表示されていることがわかります。

4.7 箱ひげグラフを作成する方法(plot.box)

[root@server ~]# cat test.py
#!/usr/bin/python3
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 箱ひげグラフを作成
df.plot.box()

# グラフを表示
plt.show()

テストプログラムを実行すると、箱ひげグラフが表示されていることがわかります。

5 グラフに汎用要素を設定する方法

5.1 タイトル、ラベルを表示する方法

4.4で作成した棒グラフに、タイトル(成績グラフ)、X軸(名前)、Y軸(点数)のラベルを表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 棒グラフを作成
df.plot.bar()

# タイトル、ラベルの設定
plt.title('成績グラフ')
plt.xlabel('名前')
plt.ylabel('点数')

# グラフを表示
plt.show()

テストプログラムを実行すると、グラフのタイトル(成績グラフ)、X軸(名前)、Y軸(点数)のラベルが表示されていることがわかります。

5.2 X軸のインデックスを表示する方法

X軸のインデックスに名前を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# DataFrameの名前列をインデックスに設定
df = df.set_index('名前')

# 棒グラフを作成
df.plot.bar(rot=0)

# タイトル、ラベルの設定
plt.title('成績グラフ')
plt.xlabel('名前')
plt.ylabel('点数')

# グラフを表示
plt.show()

テストプログラムを実行すると、X軸に名前が表示されていることがわかります。

5.3 グリッドをオンにする方法

グリッドを表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# DataFrameの名前列をインデックスに設定
df = df.set_index('名前')

# 棒グラフを作成
df.plot.bar(rot=0)

# タイトル、ラベルの設定
plt.title('成績グラフ')
plt.xlabel('名前')
plt.ylabel('点数')

# グリッドをオンにする
plt.grid(True)

# グラフを表示
plt.show()

テストプログラムを実行すると、グリッドが表示されていることがわかります。

6 個別のデータをグラフで表示する方法

これまでは読み込んだ全てのデータをグラフに表示していました。ここでは、特定の項目(例えば国語)だけのグラフの表示や、ある人物(例えば佐藤さん)だけのグラフを表示してみます。

6.1 特定の項目(数学と国語)のグラフを表示する方法

各個人の数学と国語に関する点数を棒グラフで表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv", index_col=0)

# 棒グラフを作成
df[["国語","数学"]].plot.barh()

# グリッドをオンにする
plt.grid(True)

# グラフを表示
plt.show()

テストプログラムを実行すると、各個人の数学、国語の点数が表示されていることがわかります。

6.2 特定の項目(佐藤さん)の棒グラフを表示する方法

佐藤さんの英語、数学、国語の点数を棒グラフで表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv", index_col=0)

# 棒グラフを作成
df.loc["佐藤"].plot.barh()

# タイトルを追加
plt.title("佐藤さんの成績")

# グリッドをオンにする
plt.grid(True)

# グラフを表示
plt.show()

テストプログラムを実行すると、佐藤さんの英語、数学、国語の点数が棒グラフで表示されていることがわかります。

6.3 特定の項目(佐藤さん)の円グラフを表示する方法

佐藤さんの英語、数学、国語の点数を円グラフで表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv", index_col=0)

# 円グラフを作成する
df.loc["佐藤"].plot.pie()

# グラフを表示
plt.show()

テストプログラムを実行すると、佐藤さんの英語、数学、国語の点数が円グラフで表示されていることがわかります

Y 参考図書


TECHNICAL MASTER はじめてのAlmaLinux 9 & Rocky Linux 9 Linuxサーバエンジニア入門編

TECHNICAL MASTER はじめてのAlmaLinux 9 & Rocky Linux 9 Linuxサーバエンジニア入門編 (TECHNICAL MASTER 100) 単行本(ソフトカバー)




Rocky Linux & AlmaLinux実践ガイド impress top gearシリーズ

Rocky Linux & AlmaLinux実践ガイド (impress top gear) 単行本(ソフトカバー)

サンプルコードが短く、分かりやすいです。
Pythonコードレシピ集(単行本)


Pythonコードレシピ集(Kindle版)

  • スッキリわかるPython入門 (スッキリわかる入門シリーズ)

簡潔な説明で分かりやすいです。

スッキリわかるPython入門 (スッキリわかる入門シリーズ)(単行本)

スッキリわかるPython入門 (スッキリわかるシリーズ)(Kindle版)

Webのスクレイピングについて簡潔で分かりやすく説明されています。

Python2年生 スクレイピングのしくみ 体験してわかる!会話でまなべる!(単行本)

Python2年生 スクレイピングのしくみ 体験してわかる!会話でまなべる!(Kindle版)

  • Python1年生 第2版 体験してわかる!会話でまなべる!プログラミングのしくみ


Python1年生 第2版 体験してわかる!会話でまなべる!プログラミングのしくみ(単行本)

Python 1年生 体験してわかる!会話でまなべる!プログラミングのしくみ(Kindle版)

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

pandasの使い方

1 はじめに

pandasとは、データ分析用ライブラリです。pandasで利用できるデータは、表計算ソフトExcelで扱うような表形式のデータです。pandasを使うと、Excelなどのファイルから表形式のデータを読み込み、集計、データ抽出、グラフの表示などが行えます。

2 検証環境

AlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

pythonの版数は以下のとおりです。

[root@server ~]# python -V
Python 3.9.16

3 事前準備

スシの名前、価格、注文数を記録したcsvファイルを作成します。なお、ikaの値段は意図的に空欄にしています。

[root@server ~]# cat sushi.csv
sushi,price,orders
ikura,500,2
aji,400,4
uni,700,2
toro,800,4
ika,,2
saba,200,2
kohada,200,2

4 csvファイルに対する操作

4.1 csvファイルを読み込む方法(read_csv)

csvファイルを読み込むためには、read_csvメソッドを使用します。read_csvメソッドの引数には、csvファイルのパスを指定します。

読み込んだファイル(sushi.csv)を表示するテストプログラムを作成します。なお、テストプログラム中のdfはDataFrameを表しています。Pandasには2つの主要なデータ構造があって、Series(シリーズ)が1次元のデータ、DataFrame(データフレーム)が2次元のデータに対応しています。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df)

テストプログラムを実行すると、sushi.csvの中身が表示されていることがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
0   ikura  500.0       2
1     aji  400.0       4
2     uni  700.0       2
3    toro  800.0       4
4     ika    NaN       2
5    saba  200.0       2
6  kohada  200.0       2

4.2 csvファイルに書き込む方法(to_csv)

読み込んだファイル(sushi.csv)を/tmp直下に書き込むテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
df.to_csv('/tmp/sushi.csv')

テストプログラムを実行します。

[root@server ~]# ./test.py

/tmpにファイルが書き込まれたことがわかります。

[root@server ~]# cat /tmp/sushi.csv
,sushi,price,orders
0,ikura,500.0,2
1,aji,400.0,4
2,uni,700.0,2
3,toro,800.0,4
4,ika,,2
5,saba,200.0,2
6,kohada,200.0,2

次は、指定した列を/tmp直下のファイル(test.csv)に保存してみます。ここでは、sushi列のみをファイルに保存するテストぽプログラムを作成してみます。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
df.to_csv('/tmp/sushi.csv', columns=['sushi'])

テストプログラムを実行します。

[root@server ~]# ./test.py

テストプログラムを実行すると、sushi列がファイルに保存するされていることがわかります。

[root@server ~]# cat /tmp/sushi.csv
,sushi
0,ikura
1,aji
2,uni
3,toro
4,ika
5,saba
6,kohada

あと始末をします。

[root@server ~]# rm /tmp/sushi.csv

5 DataFrameの各種情報を取得する方法

5.1 DataFrameの要約情報を取得する方法(info)

infoメソッドは、DataFrameのデータ型やメモリ使用量などを表示するメソッドです。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.info())

テストプログラムを実行すると、DataFrameのデータ型やメモリ使用量が表示されていることがわかります。

[root@server ~]# ./test.py
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7 entries, 0 to 6
Data columns (total 3 columns):
 #   Column  Non-Null Count  Dtype
---  ------  --------------  -----
 0   sushi   7 non-null      object
 1   price   6 non-null      float64
 2   orders  7 non-null      int64
dtypes: float64(1), int64(1), object(1)
memory usage: 296.0+ bytes
None

5.2 DataFrameの行数を求める方法(len)

len はPythonの組み込み関数であり、DataFrameの行数を返します。この関数には、シーケンス(文字列、バイト列、タプル、リスト、rangeなど)またはコレクション(辞書、集合、凍結集合など)を引数として渡します。

DataFrameの行数を求めるテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(len(df))

テストプログラムを実行すると、DataFrameの行数が7行であることがわかります。

[root@server ~]# ./test.py
7

5.3 DataFrameの列名を取得する方法(columns)

columnsはDataFrame の属性です。columns属性は、DataFrame内の列名を含むリストを返します。

CSVファイルからデータを読み込み、DataFrame内の列名を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.columns)

テストプログラムを実行すると、列名を含むリストが表示されていることがわかります。

[root@server ~]# ./test.py
Index(['sushi', 'price', 'orders'], dtype='object')

次は、DataFrameのカラム数を求めるテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(len(df.columns))

テストプログラムを実行すると、DataFrameのカラム数が3であることがわかります。

[root@server ~]# ./test.py
3

5.4 行数・列数を取得する方法(shape)

shape はpandasのDataFrameの属性です。DataFrameのshape 属性は、行数と列数を含むタプルを返します。このタプルの最初の要素は行数(行の数)であり、2番目の要素は列数(列の数)です。

CSVファイルからデータを読み込み、そのデータの行数と列数を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.shape)
print(df.shape[0])
print(df.shape[1])

テストプログラムを実行すると、読み込んだファイルの行数と列数が表示されていることがわかります。

[root@server ~]# ./test.py
(7, 3)
7
3

6 DataFrameの行データを取得する方法

6.1 先頭から指定した行数を取り出す方法(head)

headはDataFrameの最初のいくつかの行を取得するたのメソッドです。headに引数を指定しないと、DataFrameの最初の5行を取得します。

DataFrameの先頭2行を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.head(2))

テストプログラムを実行すると、DataFrameの先頭2行が表示されていることがわかります。

[root@server ~]# ./test.py
   sushi  price  orders
0  ikura  500.0       2
1    aji  400.0       4

6.2 末尾から指定した行数を取り出す方法(tail)

headはDataFrameの最後のいくつかの行を取得するたのメソッドです。

DataFrameの末尾1行を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.tail(1))

テストプログラムを実行すると、DataFrameの末尾1行が表示されていることがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
6  kohada  200.0       2

6.3 DataFrameの指定した行をまとめて取り出す方法(iloc)

DataFrameのindexが2から4までの行を取り出してみます。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.iloc[2:4])

テストプログラムを実行すると、DataFrameの2行目から4行目までの行が表示されていることがわかります。

[root@server ~]# ./test.py
  sushi  price  orders
2   uni  700.0       2
3  toro  800.0       4

7 DataFrameの列データを取得する方法

DataFrameのsushi列を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df['sushi'])

テストプログラムを実行すると、DataFrameのsushi列が表示されていることがわかります。

[root@server ~]# ./test.py
0     ikura
1       aji
2       uni
3      toro
4       ika
5      saba
6    kohada
Name: sushi, dtype: object

DataFrameのprice列を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df['price'])

テストプログラムを実行すると、DataFrameのprice列が表示されていることがわかります。なお、ikaの価格は未設定なので、実行結果はNaNと表示されています。

[root@server ~]# ./test.py
0    500.0
1    400.0
2    700.0
3    800.0
4      NaN
5    200.0
6    200.0
Name: price, dtype: float64

8 DataFrameの基本統計量を取得する方法

8.1 列の最大値の取り出し方(max)

maxは、最大値を取得するSeriesの属性です。Seriesは、1次元のデータを表現するために使用するデータ構造です。

CSVファイルから寿司の価格データを読み込み、その価格(price列)の最大値を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
series = df['price']
print(series.max())

テストプログラムを実行すると、価格の最大値は800円であることがわかります。

[root@server ~]# ./test.py
800.0

8.2 列の最小値の取り出し方(min)

minは、最小値を取得するSeriesの属性です。Seriesは、1次元のデータを表現するために使用するデータ構造です。

CSVファイルから寿司の価格データを読み込み、その価格(price列)の最小値を表示するテストプログラムを作成します。

200.0
[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
series = df['price']
print(series.min())

テストプログラムを実行すると、価格の最小値が200円であることがわかります。

[root@server ~]# ./test.py
200.0

8.3 平均値求める方法(mean)

meanは、平均値を取得するSeriesの属性です。Seriesは、1次元のデータを表現するために使用するデータ構造です。

CSVファイルから寿司の価格データを読み込み、その価格(preice列)の平均値を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
series = df['price']
print(series.mean())

テストプログラムを実行すると、価格の平均値が467円であることがわかります。

[root@server ~]# ./test.py
466.6666666666667
[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
mean_price = (df['price']).mean()
mean_orders = (df['orders']).mean()

print("平均 price:", mean_price)
print("平均 orders:", mean_orders)
[root@server ~]# ./test.py
平均 price: 466.6666666666667
平均 orders: 2.5714285714285716

8.4 ユニークな値を調べる方法(unique)

uniqueは、Series内のユニークな値(重複のない値)を抽出するメソッドです。

CSVファイルから寿司の価格データを読み込み、ユニークな値を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.price.unique())

テストプログラムを実行すると、200円のスシはsabaとkohadaの2つありますが、1つにまとめられていることがわかります。

[root@server ~]# ./test.py
[500. 400. 700. 800.  nan 200.]

9 フィルタリングする方法

9.1 比較演算子で行を抽出する方法(query)

queryは条件を指定してDataFrameからデータを抽出するためのメソッドです。

price列が300円未満のスシを表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.query('price < 300'))

テストプログラムを実行すると、300円未満のスシが表示されていることがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
5    saba  200.0       2
6  kohada  200.0       2

次に、price列が400円以上のスシを表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.query('not price < 400'))

テストプログラムを実行すると、400円以上のスシが表示されていることがわかります。

[root@server ~]# ./test.py
   sushi  price  orders
0  ikura  500.0       2
1    aji  400.0       4
2    uni  700.0       2
3   toro  800.0       4
4    ika    NaN       2

9.2 完全一致によるフィルタリング方法

sushi列がikuraの行を抽出するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
raw = (df.sushi == 'ikura')
print(df[raw])

テストプログラムを実行すると、ikuraの行が表示されていることがわかります。

[root@server ~]# ./test.py
   sushi  price  orders
0  ikura  500.0       2

price列が200円のスシを表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
raw = (df.price == 200)
print(df[raw])

テストプログラムを実行すると、price列が200円のスシが表示されていることがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
5    saba  200.0       2
6  kohada  200.0       2

10 DataFrameをソートする方法

10.1 昇順にソートする方法

DataFrameのprice列を昇順にソートテストプログラムを作成します。デフォルトは昇順でソートします。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.sort_values('price')

テストプログラムを実行すると、price列が昇順に表示されていることがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
5    saba  200.0       2
6  kohada  200.0       2
1     aji  400.0       4
0   ikura  500.0       2
2     uni  700.0       2
3    toro  800.0       4
4     ika    NaN       2

10.2 降順にソートする方法

DataFrameの価格(price)を降順にソートするテストプログラムを作成します。ascending=Falseを指定すると、降順にソートすることができます

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.sort_values('price', ascending=False))

テストプログラムを実行すると、価格が降順になっていることがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
3    toro  800.0       4
2     uni  700.0       2
0   ikura  500.0       2
1     aji  400.0       4
5    saba  200.0       2
6  kohada  200.0       2
4     ika    NaN       2

次に、DataFrameのsushi列を降順に並べるテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.sort_values('sushi', ascending=False))

テストプログラムを実行すると、sushi列がz->aの順で表示されていることがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
2     uni  700.0       2
3    toro  800.0       4
5    saba  200.0       2
6  kohada  200.0       2
0   ikura  500.0       2
4     ika    NaN       2
1     aji  400.0       4

11 DataFrameの値を置換する方法(replace)

replaceはSeriesやDataFrame内の値を指定した値で置換するためのメソッドです。

DataFrameのsushi列のtoroをohtoroに置換するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.replace('toro', 'ohtoro'))

テストプログラムを実行すると、toroがohtoroに置換されていることがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
0   ikura  500.0       2
1     aji  400.0       4
2     uni  700.0       2
3  ohtoro  800.0       4
4     ika    NaN       2
5    saba  200.0       2
6  kohada  200.0       2

12 行列の名前を変更する方法(rename)

renameは、DataFrameの列名や行ラベルを変更するためのメソッドです。

12.1 列の名前を変更する方法

DataFrameの列名をpriceからkakakに変更するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
df_new = df.rename(columns={'price':'kakaku'})
print(df_new)

テストプログラムを実行すると、priceがkakakuに変更されたことがわかります。

[root@server ~]# ./test.py
    sushi  kakaku  orders
0   ikura   500.0       2
1     aji   400.0       4
2     uni   700.0       2
3    toro   800.0       4
4     ika     NaN       2
5    saba   200.0       2
6  kohada   200.0       2

次に、DataFrameの2つの列名、priceをkakaku、ordersをcyumonに変更してみます。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
df_new = df.rename(columns={'price':'kakaku','orders':'cyumon'})
print(df_new)

テストプログラムを実行すると、priceがkakaku、ordersがcyumonに変更されたことがわかります。

[root@server ~]# ./test.py
    sushi  kakaku  cyumon
0   ikura   500.0       2
1     aji   400.0       4
2     uni   700.0       2
3    toro   800.0       4
4     ika     NaN       2
5    saba   200.0       2
6  kohada   200.0       2

13 欠損値の扱い方

欠損値(NaN)の扱うメソッドに、isnull,dropna,fillna,notnullがあります。各メソッドについて動作確認をしてみます。

13.1 欠損値を判定する方法(isnull)

isnullは、各要素に対して判定を行い、欠損値(NaN)であればTrue、欠損値でなければFalseと判定します。

要素が欠損値ならTrue、欠損値でなければFalseを表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.isnull())

テストプログラムを実行すると、ikaの価格は欠損しているため、Trueと表示されていることがわかります。

[root@server ~]# ./test.py
   sushi  price  orders
0  False  False   False
1  False  False   False
2  False  False   False
3  False  False   False
4  False   True   False
5  False  False   False
6  False  False   False

13.2 欠損値を削除する方法(dropna)

dropnaは、指定した条件に合致するデータ列に欠損値(NaN)が存在するかどうかをチェックし、条件を満たす場合にそのデータ列を削除するメソッドです。

欠損値(NaN)が存在するデータ列を削除するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
print(df.dropna())

テストプログラムを実行すると、欠損値が含まれるikaの行が削除されたことがわかります。

[root@server ~]# ./test.py
    sushi  price  orders
0   ikura  500.0       2
1     aji  400.0       4
2     uni  700.0       2
3    toro  800.0       4
5    saba  200.0       2
6  kohada  200.0       2

13.3 列の欠損値を補完する方法(fillna)

fillnaは欠損値を任意の値で穴埋めするメソッドです。ここでは、ikaの価格(欠損値)を平均値で補完してみます。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('sushi.csv')
series = df['price']
mean = series.mean()
filled = series.fillna(mean)
print(filled)

テストプログラムを実行すると、ikaの価格が平均値(466.666667)で補完されていることがわかります。

[root@server ~]# ./test.py
0    500.000000
1    400.000000
2    700.000000
3    800.000000
4    466.666667
5    200.000000
6    200.000000
Name: price, dtype: float64

14 日付に関する操作方法

テストプログラムが読み込みファイルを作成します。

[root@server ~]# cat date.csv
id,date,order
01,2021-08-01,3
02,2021-08-10,2
03,2021-09-10,1
04,2021-10-11,4
04,2021-10-20,5

14.1 object型からdatetime64型に変換する方法(to_datetime)

to_datetimeは日付と時刻のデータをdatetimeオブジェクトに変換するためのメソッドです。

date列の型を確認するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df=pd.read_csv('date.csv')
print(df.dtypes)

date列はobject型であることがわかります。

[root@server ~]# ./test.py
id        int64
date     object
order     int64
dtype: object

次に、object型からdatetime64型に変換してみます。datetime64型に変換することで、例えば年だけを取得したい、月だけを取得したいということができます

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('date.csv')
df["date"] = pd.to_datetime(df["date"])
print(df.dtypes)

date列がobject型からdatetime64型に変換されたことがわかります。

[root@server ~]# ./test.py
id                int64
date     datetime64[ns]
order             int64
dtype: object

14.2 列を任意のフォーマットに一括変換する方法(strftime)

dt.strftimeは、列を任意のフォーマットの文字列に一括変換する関数です。%mで月のフォーマットを指定しています。年月の場合は%Y%mと指定します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df=pd.read_csv('date.csv')
df['date']=pd.to_datetime(df['date'])
df['month']=df['date'].dt.strftime('%m')
print(df)

年月日から月に変換されたmonth列が追加されたことがわかります。

[root@server ~]# ./test.py
   id       date  order month
0   1 2021-08-01      3    08
1   2 2021-08-10      2    08
2   3 2021-09-10      1    09
3   4 2021-10-11      4    10
4   4 2021-10-20      5    10

14.3 グルーピングする方法(groupby)

データをグルーピングして、グループごとにデータを集約して、それぞれの合計/最大/最小等の統計量を算出したりすることができます。

まず、注文数について、月単位の合計数を求めてみます。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df = pd.read_csv('date.csv')
df['date'] = pd.to_datetime(df['date'])
df['month'] = df['date'].dt.strftime('%Y-%m')

monthly_orders = df.groupby('month')['order'].sum()

print(monthly_orders)

注文数が、8月は5、9月は1、10月は9であることがわかります。

[root@server ~]# ./test.py
month
2021-08    5
2021-09    1
2021-10    9
Name: order, dtype: int64

次に、各月の一番多い注文数を求めるテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

df=pd.read_csv('date.csv')
df['date']=pd.to_datetime(df['date'])
df['month']=df['date'].dt.strftime('%m')
print(df.groupby('month').max()['order'])

各月の注文の最大数は、8月は3、9月は1、10月は5であることがわかります。

[root@server ~]# ./test.py
month
08    3
09    1
10    5
Name: order, dtype: int64

15 DataFrameを結合する方法(merge)

mergeはpandasライブラリのデータ結合操作を行うためのメソッドです。mergeメソッドを使用することで、異なるDataFrameオブジェクトのデータを共通のキー(列)に基づいて結合し、新しいDataFrameを生成できます。

名前と国語の点数を記録したkokugo.csvを作成します。

[root@server ~]# cat kokugo.csv
name,kokugo
kato,70
suzuki,80
tanaka,60

名前と算数の点数を記録したsansu.csvを作成します。

[root@server ~]# cat sansu.csv
name,sansu
kato,50
suzuki,70
tanaka,90

kokugo.csvとsansu.csvを結合するテストプログラムを作成します。共通する列をonに指定することで、紐づく値を軸に結合することができます。howでkokugoとsansuのどちらを主軸にするかを指定します。今回は、kokugoにsansuを結合するので、how='left'と指定します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd

kokugo = pd.read_csv('kokugo.csv')
sansu = pd.read_csv('sansu.csv')
print(pd.merge(kokugo,sansu, on='name', how='left'))

kokugo.csvとsansu.csvに共通のキー(nama)で結合されていることがわかります。

[root@server ~]# ./test.py
     name  kokugo  sansu
0    kato      70     50
1  suzuki      80     70
2  tanaka      60     90

16 その他

16.1 複数ファイルを読み込む方法

テストプログラムが読み込むファイルを作成します。

[root@server ~]# cat data1.csv
name,age
suzuki,40
kato,50

テストプログラムが読み込むファイルを作成します。

[root@server ~]# cat data2.csv
name,age
tanaka,30
itakahashi,20

テストプログラムを作成します。
globはPythonの組み込みライブラリです。指定したパターンに一致するファイルやディレクトリのリストを取得するために使用します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import glob

all_files=glob.glob('/root/data[0-9].csv')

li = []
for filename in all_files:
  df = pd.read_csv(filename, delimiter=",")
  li.append(df)
frame = pd.concat(li, ignore_index=True)
print(frame)

テストプログラムを実行すると、

[root@server ~]# ./test.py
         name  age
0      tanaka   30
1  itakahashi   20
2      suzuki   40
3        kato   50

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

記事作成に参考にした書籍です。

サンプルコードが短く、分かりやすいです。

Pythonコードレシピ集(Kindle版)

Pythonコードレシピ集(単行本)

  • スッキリわかるPython入門 (スッキリわかる入門シリーズ)

簡潔な説明で分かりやすいです。

スッキリわかるPython入門 (スッキリわかる入門シリーズ)(単行本)

スッキリわかるPython入門 (スッキリわかるシリーズ)(Kindle版)

shredコマンドの使い方

1 shredコマンドとは?

shredコマンドは、データの復旧を困難にするためにファイルにランダムデータ(特別なパターン)を繰り返し書き込むコマンドです。通常、rmコマンドでファイルを削除すると、ファイルの内容は見かけ上は表示できなくなりますが、実際にはそのデータがディスク上に残ります。しかし、shredコマンドを使用すると、ディスク上のデータを繰り返しランダムデータで上書きすることでデータの回復ができなくなります。そのため、セキュリティを向上させることができます。

2 検証環境

AlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

shredコマンドの版数は以下のとおりです。

[root@server ~]# shred --version
shred (GNU coreutils) 8.32
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

作者 Colin Plumb。

3 オプション一覧

shredコマンドのオプションは以下のとおりです。

[root@server ~]# shred --help
使用法: shred [OPTION]... FILE...
Overwrite the specified FILE(s) repeatedly, in order to make it harder
for even very expensive hardware probing to recover the data.

If FILE is -, shred standard output.

Mandatory arguments to long options are mandatory for short options too.
  -f, --force    書き込みができるように必要に応じてアクセス権限を変更する
  -n, --iterations=N  N 回上書きを繰り返す (デフォルト: 3 回)
      --random-source=FILE  ランダムバイトのソースを FILE にする
  -s, --size=N   N で指定したバイト数 shred を行う (接尾辞として K, M, G など
                 が使用可能)
  -u             deallocate and remove file after overwriting
      --remove[=HOW]  like -u but give control on HOW to delete;  See below
  -v, --verbose  show progress
  -x, --exact    do not round file sizes up to the next full block;
                   this is the default for non-regular files
  -z, --zero     add a final overwrite with zeros to hide shredding
      --help     この使い方を表示して終了する
      --version  バージョン情報を表示して終了する

4 オプションなしで実行する方法

オプションなしでshredコマンドを実行すると、4096バイトのランダムデータを3回ファイルに上書きします。

テスト用ファイルを作成します。

[root@server ~]# echo "12345" > /tmp/test.txt

作成したテスト用ファイルを確認します。

[root@server ~]# cat /tmp/test.txt
12345

オプションを付けずにshredコマンドを実行します。

[root@server ~]# shred /tmp/test.txt

shredコマンドを実行すると。ファイルは残ったままですが、ファイルサイズが4096byteになっていることがわかります。ファイルの中身を確認すると、ランダムデータで書き換えられていることがわかります。

[root@server ~]# ls -l /tmp/test.txt
-rw-r--r--. 1 root root 4096  9月 28 19:42 /tmp/test.txt

shredコマンド実行時の様子をstraceコマンドで確認すると、/tmp/test.txtをオープンした後、4096バイトのランダムデータを/tmp/test.txtに3回書き込んでいることが確認できます。straceコマンドの使い方は、straceコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# strace -ttT -f -e trace=openat,write shred /tmp/test.txt
-snip
19:57:58.315029 openat(AT_FDCWD, "/tmp/test.txt", O_WRONLY|O_NOCTTY) = 3 <0.000460>
19:57:58.317684 write(3, "\313r\234\10\330\2669\r]\36/\252\25\254V:w\235\3245\3712\270u\26\343\216\276Y\2623P"..., 4096) = 4096 <0.000195>
19:57:58.319256 write(3, "\214W\337H\253,J\22A\227\260\7L\317\301O`;\21Q[\30\5{\301t\32\226\270&\1\332"..., 4096) = 4096 <0.000191>
19:57:58.321464 write(3, "\35\204*O\372\315Z\366\302\26\300L \337\200\247\275\274U\222Rl\343A\320}!\217\301\361S\272"..., 4096) = 4096 <0.000867>
19:57:58.325854 +++ exited with 0 +++

5 ランダムデータのサイズを指定する方法(-s)

-sオプションは、ファイルに書き込むランダムデータのサイズを指定するオプションです。デフォルトでは、ランダムデータのサイズは4096バイトです。

テスト用ファイルを作成します。

[root@server ~]# echo "12345" > /tmp/test.txt

作成したテスト用ファイルを確認します。

[root@server ~]# cat /tmp/test.txt
12345

8192バイトのランダムデータをファイルに書き込んでみました。この操作をstraceコマンドで確認すると、writeシステムコールの戻り値が8192であることから、8192バイトのランダムデータがファイルに書き込まれていることが分かります。

[root@server ~]# strace -ttT -f -e trace=openat,write shred -s 8192 /tmp/test.txt
-snip-
20:07:06.514961 openat(AT_FDCWD, "/tmp/test.txt", O_WRONLY|O_NOCTTY) = 3 <0.000048>
20:07:06.515322 write(3, "\36\10\\2aA", 6) = 6 <0.000096>
20:07:06.517121 write(3, "W\241\346\225\362\322", 6) = 6 <0.000042>
20:07:06.518447 write(3, ".\3414\201\301h", 6) = 6 <0.000036>
20:07:06.521435 write(3, "n\317\371\303}\217\22\277.z\33\354\244K\373\234\325T=\340\234n\262W\263t\274\301\4?\5\35"..., 8192) = 8192 <0.000078>
20:07:06.522752 write(3, "N%\265\3331:Mkj\345f\34V/\20\20\325\205\340\322U\376o\321\365\270Nt\250\3778\f"..., 8192) = 8192 <0.000051>
20:07:06.523538 write(3, "\301\206\240\313f\254\226aV\216\267h\307/E\215\22\375`.o\363urf\350\360\t\212\216\354S"..., 8192) = 8192 <0.000041>
20:07:06.524505 +++ exited with 0 +++

6 進捗状況を表示する方法(-v)

-vオプションは、shredコマンドの進捗状況を表示するオプションです。

テスト用ファイルを作成します。

[root@server ~]# echo "12345" > /tmp/test.txt

作成したテスト用ファイルを確認します。

[root@server ~]# cat /tmp/test.txt
12345

ファイルにランダムデータを3回書き込んでいることが分かります。

[root@server ~]# shred -v /tmp/test.txt
shred: /tmp/test.txt: 経過 1/3 (random)...
shred: /tmp/test.txt: 経過 2/3 (random)...
shred: /tmp/test.txt: 経過 3/3 (random)...

7 ファイルを削除する方法(-u)

-uオプションは、ファイルにランダムにデータを書き込んだ後、ファイルを削除するオプションです。

テスト用ファイルを作成します。

[root@server ~]# echo "12345" > /tmp/test.txt

作成したテスト用ファイルを確認します。

[root@server ~]# cat /tmp/test.txt
12345

-uオプションを指定してshredコマンドを実行します。

[root@server ~]# shred -v -u /tmp/test.txt
shred: /tmp/test.txt: 経過 1/3 (random)...
shred: /tmp/test.txt: 経過 2/3 (random)...
shred: /tmp/test.txt: 経過 3/3 (random)...
shred: /tmp/test.txt: 削除しています
shred: /tmp/test.txt: /tmp/00000000 に名前が変更されました
shred: /tmp/00000000: /tmp/0000000 に名前が変更されました
shred: /tmp/0000000: /tmp/000000 に名前が変更されました
shred: /tmp/000000: /tmp/00000 に名前が変更されました
shred: /tmp/00000: /tmp/0000 に名前が変更されました
shred: /tmp/0000: /tmp/000 に名前が変更されました
shred: /tmp/000: /tmp/00 に名前が変更されました
shred: /tmp/00: /tmp/0 に名前が変更されました
shred: /tmp/test.txt: 削除しました

テスト用に作成したファイルを確認すると、ファイルが削除されたことがわかります。

[root@server ~]# ls -l /tmp/test.txt
ls: '/tmp/test.txt' にアクセスできません: そのようなファイルやディレクトリはありません

8 ランダムデータの書き込み回数を指定する方法(-n)

-nオプションは、ランダムデータをファイルに書き込む回数を指定するオプションです。オプションを指定しないとランダムデータを3回ファイルに書き込みます。

テスト用ファイルを作成します。

[root@server ~]# echo "12345" > /tmp/test.txt

作成したテスト用ファイルを確認します。

[root@server ~]# cat /tmp/test.txt
12345

straceコマンドを使用して、ランダムデータをファイルに書き込む回数を確認してみました。テスト用ファイルをオープンした後、テスト用ファイルと同じサイズ(6バイト)のランダムデータを5回書き込んだ後、4096バイトのランダムデータを5回書き込んでいることが分かります。

[root@server ~]# strace -ttT -f -e trace=openat,write shred -n 5 /tmp/test.txt
-snip-
10:30:12.169526 openat(AT_FDCWD, "/tmp/test.txt", O_WRONLY|O_NOCTTY) = 3 <0.000160>
10:30:12.170725 write(3, "\317;\236\216\"-", 6) = 6 <0.000230>
10:30:12.172615 write(3, "\0\0\0\0\0\0", 6) = 6 <0.000339>
10:30:12.174522 write(3, "~\263J\277\214\360", 6) = 6 <0.000169>
10:30:12.176208 write(3, "\377\377\377\377\377\377", 6) = 6 <0.000202>
10:30:12.178187 write(3, "\265\300-\330kB", 6) = 6 <0.000238>
10:30:12.180110 write(3, "0\274\344\337\225*\221\225\10\320\360\200\273\10@\341tc\251\"`\26\30\326\3z\233elTB1"..., 4096) = 4096 <0.000167>
10:30:12.182488 write(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 4096) = 4096 <0.000234>
10:30:12.184468 write(3, "\226\v\320\26\20\314\232B\371\345\233r:\217~D\37\206\30r\225h\334\241\351\340j\210\252\36\32\310"..., 4096) = 4096 <0.000183>
10:30:12.187589 write(3, "\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377\377"..., 4096) = 4096 <0.000182>
10:30:12.189164 write(3, ".)\260^ U\30[+h!\263\206\3655\377\304\222\335j\4+@)\16Ij\273\210\n8\263"..., 4096) = 4096 <0.000357>
10:30:12.192114 +++ exited with 0 +++

9 ランダムデータで上書きをしたあと0で上書きする方法(-z)

-zオプションは、ランダムデータをファイルに書き込んだ後、最後にゼロをファイルに書き込むオプションです。

テスト用ファイルを作成します。

[root@server ~]# echo "12345" > /tmp/test.txt

作成したテスト用ファイルを確認します。

[root@server ~]# cat /tmp/test.txt
12345

-zオプションを指定してshredコマンドを実行します。このとき、-vオプションも使用してみます。ランダムデータをファイルに3回書き込んだ後、最後にゼロを書き込んでいることが分かります。

[root@server ~]# shred -v -z /tmp/test.txt
shred: /tmp/test.txt: 経過 1/4 (random)...
shred: /tmp/test.txt: 経過 2/4 (random)...
shred: /tmp/test.txt: 経過 3/4 (random)...
shred: /tmp/test.txt: 経過 4/4 (000000)...

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

BeautifulSoupライブラリの使い方

1 Beautiful Soupとは?

Beautiful Soupは、HTMLファイルを解析および処理するためのライブラリです。このライブラリを使って、まずはローカルマシン上で基本的な使い方を練習し、その後、以下のウェブサイトから情報を抽出するテストプログラムを作成してみます。なお作成するテストプログラムですが、見やすくするため、例外処理等は実装していません。

  1. Anacondaのインストールシェルプログラムの一覧
  2. 歴代首相の名前の一覧
  3. 夏の高校野球全国高等学校野球選手権大会)歴代優勝校の一覧

2 検証環境

AlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

pythonの版数は以下のとおりです。

[root@server ~]# python -V
Python 3.9.16

3 BeautifulSoupのインストール方法

pipコマンドでbeautifulsoup4ライブラリをインストールします。

[root@server ~]# pip install beautifulsoup4

beautifulsoup4の版数を確認します。

[root@server ~]# pip show beautifulsoup4
Name: beautifulsoup4
Version: 4.12.2
Summary: Screen-scraping library
Home-page:
Author:
Author-email: Leonard Richardson <leonardr@segfault.org>
License:
Location: /usr/local/lib/python3.9/site-packages
Requires: soupsieve
Required-by:

4 事前準備

BeautifulSoupの使い方に慣れるため、ローカルマシンでHTMLファイルを作成して、それを読み込むテストプログラムを作成してみます。

[root@server ~]# vi index.html
[root@server ~]# cat index.html
<!DOCTYPE html>
<html>
<head>
    <title>サンプルHTML</title>
</head>
<body>
    <div id="chapter1">
        <h1>これは見出し1です</h1>
        <h2>これは見出し2です</h2>
        <h3>これは見出し3です</h3>
        <p>これは段落です。段落はテキストを表示するために使用されます。</p>
        <ul>
            <li>リスト項目1</li>
            <li>リスト項目2</li>
        </ul>
        <ol>
            <li>項目1</li>
            <li>項目2</li>
        </ol>
        <dl>
            <dt>用語1</dt>
            <dd>用語1の説明</dd>
        </dl>
    </div>
    <div id="chapter2">
      <a href="https://www.example.com" target="_blank">www.example.comへのリンクです。aタグにtarget属性が設定されています。クリックすると新しいタブが開きます</a>
      <br>
      <a href="https://www.kantei.go.jp" title="首相官邸">首相官邸へのリンクです。aタグにtitle属性が設定されています。マウスカーソルをリンクに合わせると、ツールチップが表示されます。</a>
    </div>
</body>
</html>

作成したHTMLファイルを/var/www/htmlにコピーします。

[root@server ~]# cp index.html /var/www/html/

httpdサービスを起動します。

[root@server ~]# systemctl start httpd

httpdプロセスがListenしているTCPの80番ポートを解放します。なお、firewall-cmdコマンドの使い方は、firewall-cmdの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# firewall-cmd --add-port=80/tcp
success

解放しているポート番号を確認します。80番ポートが解放されていることがわかります。

[root@server ~]# firewall-cmd --list-ports
80/tcp

httpdに対してcurlコマンドを実行すると、HTMLファイルを読み出していることがわかります。なお、curlコマンドの使い方は、curlコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# curl http://192.168.1.200
<!DOCTYPE html>
<html>
<head>
    <title>サンプルHTML</title>
-snip-

5 メソッドの使い方

Beautiful Soupオブジェクトは、HTMLファイルから特定の条件を満たすタグを取得するのに、findやfind_allというメソッドを利用することができます。

メソッド 概要
find 引数に指定した条件に合致する最初の要素を取得します。一致する要素が複数ある場合でも、最初に見つかった要素だけが返されます
find_all 指定した条件に合致するすべての要素を取得します。複数の要素が一致する場合、それらの要素がリストとして返されます。

5.1 findメソッドの使い方(タグ名を指定した場合)

指定したタグのタグオブジェクトおよびテキストを返すテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3

import requests
from bs4 import BeautifulSoup

response = requests.get("http://192.168.1.200/index.html")
soup = BeautifulSoup(response.content, "html.parser")
print(soup.find("title"))
print(soup.find("h1"))
print(soup.find("li"))

print(soup.find("title").text)
print(soup.find("h1").text)
print(soup.find("li").text)

テストプログラムを実行すると、1行目から3行目までがHTMLのタグを表しており、4行目から6行目までがそれらのタグに含まれるテキストを表示していることが確認できます。

[root@server ~]# ./test.py
<title>サンプルHTML</title>
<h1>これは見出し1です</h1>
<li>リスト項目1</li>
サンプルHTML
これは見出し1です
リスト項目1

5.2 findメソッドの使い方(属性を指定した場合)

属性がchapter2のコンテンツだけを表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3

import requests
from bs4 import BeautifulSoup

response = requests.get("http://192.168.1.200/index.html")
soup = BeautifulSoup(response.content, "html.parser")
content = soup.find(id="chapter2")
print(content)

テストプログラムを実行すると、chapter2という属性を持つコンテンツだけが表示されます。chapter1属性を持つコンテンツは表示されません。

[root@server ~]# ./test.py
<div id="chapter2">
<a href="https://www.example.com" target="_blank">www.example.comへのリンクです。<a>タグにtarget属性が設定されています。クリックすると新しいタブが開きます</a>
<br/>
<a href="https://www.kantei.go.jp" title="首相官邸">首相官邸へのリンクです。<a>タグにtitle属性が設定されています。マウスカーソルをリンクに合わせると、ツールチップが表示されます。</a>
</div>

5.3 find_allメソッドの使い方

liタグのテキストを全て表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3

import requests
from bs4 import BeautifulSoup

response = requests.get("http://192.168.1.200/index.html")
soup = BeautifulSoup(response.content, "html.parser")
for element in soup.find_all("li"):
    print(element.text)

テストプログラムを実行すると、全てのliタグのテキストが表示されていることがわかります。

[root@server ~]# ./test.py
リスト項目1
リスト項目2
項目1
項目2

6 タグから情報を取得する方法

タグから情報を取得するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3

import requests
from bs4 import BeautifulSoup

response = requests.get("http://192.168.1.200/index.html")
soup = BeautifulSoup(response.content, "html.parser")
content = soup.find("a")
print(content)
print(content.name)
print(content.text)
print(content.attrs)
print(content.get("href"))

テストプログラムを実行します。1行目はタグ、2行目はタグ名、3行目はタグのテキスト、4行目は属性、5行目は指定した属性の値が表示されていることがわかります。

[root@server ~]# ./test.py
<a href="https://www.example.com" target="_blank">www.example.comへのリンクです。aタグにtarget属性が設定されています。クリックすると新しいタブが開きます</a>
a
www.example.comへのリンクです。aタグにtarget属性が設定されています。クリックすると新しいタブが開きます
{'href': 'https://www.example.com', 'target': '_blank'}
https://www.example.com

7 実践練習1(Anacondのソースコードダウンロード)

Anacondaのウェブページで提供されている以下のソースコード(赤枠内)から、拡張子が.shのファイルをダウンロードするテストプログラムを作成します。
https://repo.anaconda.com/archive/

AnacondaのwebページからHTMLファイルを取得するテストプログラムを作成します。このテストプログラムを実行することで、Anacondaのwebページがどのような構成になっているかを確認することができます。

[root@server ~]# cat test.py
#!/usr/bin/python3

import requests
from bs4 import BeautifulSoup

response = requests.get("https://repo.anaconda.com/archive/")
soup = BeautifulSoup(response.content, "html.parser")
print(soup)

テストプログラムを実行します。このときlessコマンドを併用しながら、AnacondaのwebサイトのHTMLファイルの構成を確認します。拡張子が.shのファイルは、タグ内に存在することがわかります。

<tr>
<td><a href="Anaconda3-2023.07-2-Linux-x86_64.sh">Anaconda3-2023.07-2-Linux-x86_64.sh</a></td>
<td class="s">1015.6M</td>
<td>2023-08-04 10:56:05</td>
<td>589fb34fe73bc303379abbceba50f3131254e85ce4e7cd819ba4276ba29cad16</td>
</tr>
<tr>

テストプログラムを改良して、AnacondaのウェブサイトのHTMLからタグだけを表示するようにします。

[root@server ~]# cat test.py
#!/usr/bin/python3

import requests
from bs4 import BeautifulSoup

response = requests.get("https://repo.anaconda.com/archive/")
soup = BeautifulSoup(response.content, "html.parser")
a_tags = soup.find_all("a")
for a_tag in a_tags:
    print(a_tag)

テストプログラムを実行すると、タグの内容が表示されていることがわかります。

[root@server ~]# ./test.py
<a href=".winzip/">.winzip/</a>
<a href="Anaconda3-2023.07-2-Windows-x86_64.exe">Anaconda3-2023.07-2-Windows-x86_64.exe</a>
<a href="Anaconda3-2023.07-2-MacOSX-x86_64.sh">Anaconda3-2023.07-2-MacOSX-x86_64.sh</a>
<a href="Anaconda3-2023.07-2-MacOSX-x86_64.pkg">Anaconda3-2023.07-2-MacOSX-x86_64.pkg</a>
-snip-

テストプログラムを改良して、タグからhref属性で参照されるリンクだけを表示し、かつ拡張子が "sh" のリンクだけを抽出するようにします。

[root@server ~]# cat test.py
#!/usr/bin/python3

import requests
from bs4 import BeautifulSoup

response = requests.get("https://repo.anaconda.com/archive/")
soup = BeautifulSoup(response.content, "html.parser")

file_links = []
a_tags = soup.find_all("a")
for a_tag in a_tags:
    url = a_tag.get("href")
    if url.endswith(".sh"):
        file_links.append(url)
        print(url)

テストプログラムを実行すると、表示される内容は拡張子が.shであるリンクだけであることがわかります。

[root@server ~]# ./test.py
Anaconda3-2023.07-2-MacOSX-x86_64.sh
Anaconda3-2023.07-2-MacOSX-arm64.sh
Anaconda3-2023.07-2-Linux-x86_64.sh
-snip-

テストプログラムを改良して、ダウンロードするファイルを保存するディレクトリを作成します。そして、作成したディレクトリに拡張子が.shのファイルを2つだけ保存します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import os
import requests
from bs4 import BeautifulSoup

# AnacondaのサイトからHTMLファイルを取得する。
archive_url = "https://repo.anaconda.com/archive/"
response = requests.get(archive_url)
soup = BeautifulSoup(response.content, "html.parser")

# 拡張子がshのファイルだけリンクをfile_linksに格納する。
file_links = []
a_tags = soup.find_all("a")
for a_tag in a_tags:
    url = a_tag.get("href")
    if url.endswith(".sh"):
        file_links.append(url)

# ダウンロードしたファイルを格納するディレクトリを作成する
download_dir = "./download/"
if not os.path.exists(download_dir):
    os.mkdir(download_dir)

# Anacondaのサイトから拡張子がshのファイルを2つダウンロードする。
for i, file_link in enumerate(file_links[:2], 1):
    file_name = file_link.split("/")[-1]
    file_url = archive_url + file_link
    file_path = download_dir + file_name
    print(f"ダウンロード {i}: {file_path} : {file_url}")
    response = requests.get(file_url)
    with open(file_path, "wb") as file:
        file.write(response.content)

テストプログラムを実行します。拡張子がshのファイルを2つだけダウンロードします。

[root@server ~]# ./test.py
ダウンロード 1: ./download/Anaconda3-2023.07-2-MacOSX-x86_64.sh : https://repo.anaconda.com/archive/Anaconda3-2023.07-2-MacOSX-x86_64.sh
ダウンロード 2: ./download/Anaconda3-2023.07-2-MacOSX-arm64.sh : https://repo.anaconda.com/archive/Anaconda3-2023.07-2-MacOSX-arm64.sh

ダウンロードしたファイルを確認します。

[root@server ~]# ls -l download/*
-rw-r--r--. 1 root root 676990792  9月 16 23:37 download/Anaconda3-2023.07-2-MacOSX-arm64.sh
-rw-r--r--. 1 root root 641855956  9月 16 23:36 download/Anaconda3-2023.07-2-MacOSX-x86_64.sh

8 実践練習2(歴代首相の一覧)

以下のページに記載されている歴代首相の名前と年を一覧として表示してみます。
https://www.kantei.go.jp/jp/rekidainaikaku/index.html

歴代首相が記載されていwebページからHTMLファイルを取得するテストプログラムを作成します。このテストプログラムを実行することで、webページがどのような構成になっているかを確認することができます。

[root@server ~]# cat test.py
#!/usr/bin/python3
import os
import requests
from bs4 import BeautifulSoup

url = "https://www.kantei.go.jp/jp/rekidainaikaku/index.html"
response = requests.get(url)
soup = BeautifulSoup(response.content, "html.parser")
print(soup)

テストプログラムを実行すると、歴代首相の代と名前が以下の構成で繰り返し出現していることがわかります。

<div class="his-profile">
<h3 class="his-generation">第101代</h3>
<p class="his-name"><a href="/jp/rekidainaikaku/101.html">岸田 文雄</a></p>
</div>

テストプログラムを修正します。h3タグ、pタグのclass属性を指定して歴代首相の名前と代を抽出します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import os
import requests
from bs4 import BeautifulSoup

url = "https://www.kantei.go.jp/jp/rekidainaikaku/index.html"
response = requests.get(url)
soup = BeautifulSoup(response.content, "html.parser")

prime_minister_elements = soup.find_all("div", class_="his-profile")

for i, prime_minister_element in enumerate(prime_minister_elements, start=1):
    generation_element = prime_minister_element.find("h3", class_="his-generation")
    name_element = prime_minister_element.find("p", class_="his-name")

    if generation_element and name_element:
        generation = generation_element.text.strip()
        name = name_element.text.strip()
        print(f"{generation}:{name}")

テストプログラムを実行します。

[root@server ~]# ./test.py
第101代:岸田 文雄
第100代:岸田 文雄
第99代:菅 義偉
第98代:安倍 晋三
第97代:安倍 晋三
第96代:安倍 晋三
第95代:野田 佳彦
第94代:菅 直人
第93代:鳩山 由紀夫
第92代:麻生 太郎
-snip-

9 実践練習3(高校野球歴代優勝校の一覧)

以下のページに記載されている歴代の優勝校と優勝年を一覧として表示してみます。
https://www.hanshin.co.jp/koshien/highschool/past/champion/summer.html

8章のようなテストプログラムを作成して実行すると、歴代の優勝高校と優勝年が以下の構成で繰り返し出現していることがわかります。

<tr>
<td>1</td>
<td>大正4年(1915)</td>
<td>京都二中(京都)</td>
</tr>

HTMLファイルの構成を考慮してテストプログラムを作成します。優勝年と優勝校はタグに含まれていることがわかります。

[root@server ~]# cat test.py
#!/usr/bin/python3
import os
import requests
from bs4 import BeautifulSoup

url = "https://www.hanshin.co.jp/koshien/highschool/past/champion/summer.html"

response = requests.get(url)
soup = BeautifulSoup(response.content, "html.parser")

champion_list = soup.find_all("tr")
current_year_group = None

for row in reversed(champion_list):
    columns = row.find_all("td")
    if len(columns) == 3:
        year_text = columns[1].text.strip()
        school_text = columns[2].text.strip()

        year_start = year_text.find("(")
        year_end = year_text.find(")")
        if year_start != -1 and year_end != -1:
            year = year_text[year_start + 1:year_end]
            school = school_text.split("(")[0]
            print(f"{year}: {school}")

テストプログラムを実行すると、歴代の優勝高校と優勝年が表示されていることがわかります。

[root@server ~]# ./test.py
2022: 仙台育英
2021: 智辯和歌山
2020: 新型コロナウイルス感染症の流行により中止
2019: 履正社
2018: 大阪桐蔭
-snip-

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

記事作成に参考にした書籍です。

  • Pythonコードレシピ集

サンプルコードが短く、分かりやすいです。
Pythonコードレシピ集(単行本)


Pythonコードレシピ集(Kindle版)

  • スッキリわかるPython入門 (スッキリわかる入門シリーズ)

簡潔な説明で分かりやすいです。

スッキリわかるPython入門 (スッキリわかる入門シリーズ)(単行本)

スッキリわかるPython入門 (スッキリわかるシリーズ)(Kindle版)

osモジュール,os.pathモジュールの使い方

1 はじめに

ファイルとディレクトリ操作の機能を提供するosモジュールとos.pathモジュールを使ったプログラムを作成して動作確認をしてみます。

2 検証環境

AlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

pythonの版数は以下のとおりです。

[root@server ~]# python -V
Python 3.9.16

3 osモジュールの使い方

osモジュールは、OSに依存する機能を提供するモジュールです。 ファイルやディレクトリの操作を行うことができます。osモジュールが提供する関数(一部)を以下に示します。

関数 概要
getcwd カレントディレクトリ取得する
chdir カレントディレクトリを変更する
listdir ディレクトリの中身をリストで取得する
walk ディレクトリツリーを取得する
mkdir ディレクトリを作成する
rmdir ディレクトリを削除する
makedirs ディレクトリを再帰的に作成する
rename ファイルやディレクトリの名前を変更する
chmod ファイルのパミッションを変更する

3.1 カレントディレクトリを取得、変更する方法(getcwd,chdir)

カレントディレクトリのパスを取得したあと、テストプログラムの引数に指定したパスにカレントディレクトリを変更するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import os

dir = os.getcwd()
print(dir)

dir = input("ディレクトリ名を入力してください: ")
os.chdir(dir)
dir = os.getcwd()
print(dir)

テストプログラムを実行すると、カレントディレクトリを表示したあと、指定したディレクトリにカレントディレクトリを変更したことがわかります。

[root@server ~]# ./test.py
/root
ディレクトリ名を入力してください: /var/log
/var/log

3.2 ファイル一覧を取得する方法(listdir)

引数に指定したディレクトリのファイル一覧を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import os

dir = input("ディレクトリ名を入力してください: ")
files = os.listdir(dir)
print(files)

テストプログラムを実すると、/boot直下のファイルがリスト形式で表示されることがわかります。

[root@server ~]# ./test.py
ディレクトリ名を入力してください: /boot
['efi', 'grub2', 'loader', 'vmlinuz-5.14.0-284.11.1.el9_2.x86_64', 'System.map-5.14.0-284.11.1.el9_2.x86_64', 'config-5.14.0-284.11.1.el9_2.x86_64', '.vmlinuz-5.14.0-284.11.1.el9_2.x86_64.hmac', 'symvers-5.14.0-284.11.1.el9_2.x86_64.gz', 'initramfs-5.14.0-284.11.1.el9_2.x86_64.img', 'vmlinuz-0-rescue-ae58d7b0f9374e8d801cd68b6296a573', 'initramfs-0-rescue-ae58d7b0f9374e8d801cd68b6296a573.img', 'initramfs-5.14.0-284.11.1.el9_2.x86_64kdump.img']

リスト形式ではなく、lsコマンドを実行した時のように表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import os

dir = input("ディレクトリ名を入力してください: ")
files = os.listdir(dir)
for file in files:
    print(file)

テストプログラムを実行すると、/boot直下のファイルがlsコマンドを実行したときのように表示されることがわかります。

[root@server ~]# ./test.py
ディレクトリ名を入力してください: /boot
efi
grub2
loader
-snip-

3.3 ディレクトリのファイルを再帰的に表示する方法(walk)

テスト用のディレクトリ、ファイルを作成します。

[root@server ~]# mkdir -p dir1/dir2
[root@server ~]# touch dir1/file1.txt
[root@server ~]# touch dir1/dir2/file2.txt

テストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import os

for root, dirs, files in os.walk("dir1"):
    for dir in dirs:
        print("Directory:", os.path.join(root, dir))
    for file in files:
        print("File:", os.path.join(root, file))

テストプログラムを実行します。

[root@server ~]# ./test.py
Directory: dir1/dir2
File: dir1/file1.txt
File: dir1/dir2/file2.txt

3.4 ディレクトリを作成・削除する方法(mkdir,rmdir)

引数に指定したディレクトリの作成、削除をするテストプログラムを作成します。指定したディレクトリが存在しなければ作成しますが、存在すれば作成しません。

[root@server ~]# cat test.py
#!/usr/bin/python3
import os

new_dir = input("ディレクトリ名を入力してください: ")
if os.path.exists(new_dir):
  print(f"'{new_dir}' が存在するので、作成できません")
else:
  print(f"'{new_dir}' を作成します ")
  os.mkdir(new_dir)

テストプログラムを実行します。初回実行時は/tmp/testが存在しないので、ディレクトリを作成できます。

[root@server ~]# ./test.py
ディレクトリ名を入力してください: /tmp/test
'/tmp/test' を作成します

2回目は/tmp/testが存在するので、ディレクトリが作成できないことがわかります。

[root@server ~]# ./test.py
ディレクトリ名を入力してください: /tmp/test
'/tmp/test' が存在するので、作成できません

3.5 ディレクトリを再帰的に作成する方法(makedirs)

[root@server ~]# cat test.py
#!/usr/bin/python3
import os

new_dir = input("ディレクトリ名を入力してください: ")
if not os.path.exists(new_dir):
    os.makedirs(new_dir)
    print(f"'{new_dir}' を作成します ")
else:
    print(f"'{new_dir}' が存在するので、作成できません")
[root@server ~]# ./test.py
ディレクトリ名を入力してください: /tmp/dir1/dir2/dir3
'/tmp/dir1/dir2/dir3' を作成します

作成したディレクトリを確認します。/tmp直下にdir1/dir2/dir3が作成されていることがわかります。

[root@server ~]# ls -ld /tmp/dir1/dir2/dir3
drwxr-xr-x. 2 root root 6  9月  9 22:25 /tmp/dir1/dir2/dir3

3.6 ファイルやディレクトリの名前を変更する方法(rename)

renameはファイルやディレクトリの名前を変更する関数です。

[root@server ~]# cat test.py
#!/usr/bin/python3
import os

old_filename = input("ファイル名を入力してください: ")

with open(old_filename, "w") as file:
    file.write("0123456789\n")

with open(old_filename, "r") as file:
    old_data = file.read()
    print(f"作成したファイルの内容:{old_data}")

new_filename = input("変更後のファイル名を入力してください: ")
os.rename(old_filename, new_filename)

テストプログラムを実行します。

[root@server ~]# ./test.py
ファイル名を入力してください: old.txt
作成したファイルの内容:0123456789

変更後のファイル名を入力してください: new.txt
[root@server ~]# cat new.txt
0123456789

4 os.pathモジュールの使い方

os.path モジュールはファイルパスやディレクトリパスを操作するための関数を提供するモジュールです。

関数 概要
exists ファイルやディレクトリが存在するかどうかを確認します
dirname ファイルパスからディレクトリ名を取得する
basename ファイルパスからファイル名を取得する
splitext ファイル名から拡張子を取り出します
relpath 相対パスを取得します
abspath 相対パス絶対パスに変換します
join パスを結合します
split パス名からディレクトリ名とファイル名を取り出します

4.1 ファイルやディレクトリの存在確認する方法(exitst)

引数に指定したディレクトリ名やファイル名が存在するかどうかを確認するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import os

path = input("パス名を入力してください: ")

if os.path.exists(path):
    print(f"{path} は存在します.")
else:
    print(f"{path} は存在しません.")

ディレクトリの存在確認をしてみます。

[root@server ~]# ./test.py
パス名を入力してください: /etc
/etc は存在します.

ファイルの存在確認をしてみます。

[root@server ~]# ./test.py
パス名を入力してください: /etc/chrony.conf
/etc/chrony.conf は存在します.

4.2 パス名からディレクトリ名とファイル名を取り出す方法(dirname,basename)

dirnameはパス名からディレクトリ名を取り出す関数、basenameはパス名からファイル名を取り出す関数です。

[root@server ~]# cat test.py
#!/usr/bin/python3
import os

path = input("パス名を入力してください: ")

dir = os.path.dirname(path)
file = os.path.basename(path)

print(f"dir={dir}, file={file}")

テストプログラムを実行します。パス名に/etc/chrony.confを指定すると、ディレクトリ名が/efc、ファイル名がchrony.confであることがわかります。

[root@server ~]# ./test.py
パス名を入力してください: /etc/chrony.conf
dir=/etc, file=chrony.conf

4.3 拡張子を取り出す方法(splitext)

拡張子を取りだすテストプログラムを実行します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import os

path = input("パス名を入力してください: ")
file_name, file_extension = os.path.splitext(path)

print(f"ファイル名:{file_name}, 拡張子:{file_extension}")

chrony.confファイルの拡張子を確認します。拡張子がconfであることがわかります。

[root@server ~]# ./test.py
パス名を入力してください: /etc/chrony.conf
ファイル名:/etc/chrony, 拡張子:.conf

カーネルモジュールの拡張子を確認します。拡張子がxzであることがわかります

[root@server ~]# ./test.py
パス名を入力してください: /usr/lib/modules/5.14.0-284.11.1.el9_2.x86_64/kernel/net/ipv4/ip_gre.ko.xz
ファイル名:/usr/lib/modules/5.14.0-284.11.1.el9_2.x86_64/kernel/net/ipv4/ip_gre.ko, 拡張子:.xz

4.4 パス名からディレクトリ名とファイル名を求めるする方法(split)

[root@server ~]# cat test.py
#!/usr/bin/python3
import os

dir, file = os.path.split("/var/log/messages")
print(f"dir={dir}, file={file}")

テストプログラムを実行すると、/var/log/messagesからディレクトリ名(/var/log)とファイ名(messages)がそれぞれ取得できていることがわかります。

[root@server ~]# ./test.py
dir=/var/log, file=messages

4.5 パスを結合する方法(join)

joinメソッドは、引数で指定したパスを結合するメソッドです。この方法を使うと、ファイルシステムの階層構造に関する違いを気にせずにパスを作成できます。また、プラットフォーム間での互換性が確保されます。

joinメソッドの書式は以下のとおりです。

os.path.join("path1", "path2", "path3", ...)

"/"、"var"、"log”を結合するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import os

log = os.path.join("/", "var", "log")
print(log)

テストプログラムを実行すると、引数に指定したパスが結合されて/var/logと表示されることがわかります。

[root@server ~]# ./test.py
/var/log

4.6 相対パス絶対パスを求める方法(relpath,abspath)

テスト用のディレクトリを作成します。

[root@server ~]# mkdir dir1/dir2/dir3

テスト用に作成したディレクトリの相対パス絶対パスを求めるテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import os

relative_path = "./dir1/dir2/dir3"
absolute_path = os.path.abspath(relative_path)

print(f"相対パス:{relative_path}, 絶対パス:{absolute_path}")

テストプログラムを実行すると、テスト用に作成したディレクトリの相対パス絶対パスが表示されることがわかります。

[root@server ~]# ./test.py
相対パス:./dir1/dir2/dir3, 絶対パス:/root/dir1/dir2/dir3

5 ファイルオブジェクトの使い方

5.1 read系メソッド

5.1.1 ファイル全体を読み込む方法(read)

readメソッドは、ファイル全体または指定したバイト数を読み込むためのメソッドです。readメソッドを引数なしで呼び出すとファイル全体を読み込みます。readメソッドに引数を指定すると、指定したバイト数を読み出します。

ファイル全体を読み出すテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3

with open("/tmp/test.txt", "r") as file:
    file_contents = file.read()
    print(file_contents)

テストプログラムが読み込むファイルを作成します。

[root@server ~]# vi /tmp/test.txt
[root@server ~]# cat /tmp/test.txt
111 111
222 222 222

テストプログラムを実行すると、ファイル全体読み込んでいることがわかります。

[root@server ~]# ./test.py
111 111
222 222 222

[root@server ~]#

次は、readメソッドの引数に2を指定して、ファイルから2バイトを読み込むテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3

with open("/tmp/test.txt", "r") as file:
    file_contents = file.read(2)
    print(file_contents)

テストプログラムを実行すると、ファイルから2バイト読み込んでいることがわかります。

[root@server ~]# ./test.py
11
5.1.2 ファイルから1行ずつ読み込む方法(readline)

readlineメソッドはファイルから1行ずつ読み込むためのメソッドです。ファイル終端に到達すると空文字を返します。

[root@server ~]# cat test.py
#!/usr/bin/python3

with open("/tmp/test.txt", "r") as f:
    line = f.readline()
    while line:
        print(line, end="")
        line = f.readline()

テストプログラムを実行します。

[root@server ~]# ./test.py
111 111
222 222 222
5.1.3 1行をリスト形式で取得する方法(readlines)

readlinesメソッドはファイルから全ての行を一度に読み取り、各行を文字列のリストとして返すためのメソッドです。

[root@server ~]# cat test.py
#!/usr/bin/python3

with open("/tmp/test.txt", "r") as f:
    lines = f.readlines()
    print(lines)

テストプログラムを実行します。

[root@server ~]# ./test.py
['111 111\n', '222 222 222\n']

5.2 write系メソッド

5.2.1 ファイルにデータを書き込む方法(write)

writeメソッドは、ファイルにデータを書き込むためのメソッドです。

引数に指定したファイルにデータを書き込むテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3

file = input("ファイル名を入力してください: ")
with open(file, "w") as file:
    file.write("0123456789\n")

テストプログラムを実行して、/tmp/sample.txtを作成してみます。

[root@server ~]# ./test.py
ファイル名を入力してください: /tmp/sample.txt

作成したファイルの中身を確認します。

[root@server ~]# cat /tmp/sample.txt
0123456789

5.3 seekメソッド

seekメソッドは、ファイルオブジェクト内のカーソル位置を移動するためのメソッドです。書式は以下の通りす。

seek(offset, whence)
引数 意味
offset 移動するバイト数を指定します。正の整数値はファイルの先頭から移動し、負の整数値はファイルの末尾から逆方向に移動します
whence デフォルトは0でファイルの先頭からの相対位置を示します。1は現在の位置からの相対位置を示し、2はファイルの末尾からの相対位置を示します

テストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3

seek = int(input("移動するバイト数を入力してください: "))
byte = int(input("読み込むバイト数を入力してください: "))

with open("/tmp/test.txt", "r") as f:
    f.seek(seek)
    data = f.read(byte)
    print(data)

テストプログラムが読み込むファイルを作成します。

[root@server ~]# vi /tmp/test.txt
[root@server ~]# cat /tmp/test.txt
0123456789

ファイル先頭から2バイト読み出してみます。

[root@server ~]# ./test.py
移動するバイト数を入力してください: 0
読み込むバイト数を入力してください: 2
01

ファイル先頭から1バイト移動してから5バイト読み出してみます。

[root@server ~]# ./test.py
移動するバイト数を入力してください: 1
読み込むバイト数を入力してください: 5
12345

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

記事作成に参考にした書籍です。

サンプルコードが短く、分かりやすいです。

Pythonコードレシピ集(Kindle版)

Pythonコードレシピ集(単行本)

  • スッキリわかるPython入門 (スッキリわかる入門シリーズ)

簡潔な説明で分かりやすいです。

スッキリわかるPython入門 (スッキリわかる入門シリーズ)(単行本)

スッキリわかるPython入門 (スッキリわかるシリーズ)(Kindle版)