OS

【Linuxコマンド】便利な小技たち(TIPS)

【※ 当記事は2020年7月2日時点の情報です】

ペイヴメント(@pavement1234)です。

エンジニア
エンジニア
Linuxコマンドの便利な小技たちを知りたい。

①vmstat:システム監視
②top:CPU使用率監視
③time:コマンドの実行時間を計測
④strace:システムコールのスタックトレースを確認
⑤キャッシュをクリアする方法
⑥grep:文字列検索
⑦find:ファイル検索
⑧プロセスのバックグラウンド実行
⑨ドットコマンド(.)
⑩Linuxデスクトップのスクリーンキャプチャー
⑪リダイレクト(2>&1)の意味

①vmstat:システム監視

プロセス(procs)、
メモリ(memory)、
スワップ(swap)、
ブロックI/O(io)、
システム情報(system)、
CPU(cpu)の統計情報を表示します。

vmstatをオプション無しで実行

$ vmstat

procs r:実行中と実行待ち中のプロセス数合計
b:割り込み不可能なスリープ状態のプロセス数
memory swpd:使用中の仮想メモリ量
free:空きメモリ量
buff:バッファキャッシュに使用しているメモリ量
cache:キャッシュに使用しているメモリ量
inact:非アクティブなメモリ量
active:アクティブなメモリ量
swap si:ディスクからスワップインしているメモリ量
so:ディスクにスワップアウトしているメモリ量
io bi:HDD等のブロック型デバイスから受け取ったブロック数
bo:ブロック型デバイスに送ったブロック数
system in:1秒あたりの割り込み回数
cs:コンテキストスイッチ回数
cpu us:ユーザランドの実行時間
sy:カーネルモード(システム)の実行時間
id:アイドル時間
wa:I/O待ち時間
st:仮想マシンから奪われた時間

縦に表示するには-sオプション

$ vmstat -s

vmstat 秒数 回数

1秒間に10回表示。

$ vmstat 1 10

②top:CPU使用率監視

LinuxプロセスのCPU使用率をモニターしたくなることって、
結構あります(組み込みだと特に)。
そんなとき、topコマンドが便利です。

topをオプション無しで実行

$ top

topコマンドを実行すると、
1秒毎にプロセス状態がモニターできます。
これだと1秒毎に情報がリフレッシュされてしまうので、
CPU使用率がどのように変わっていくかを記録することが難しいです。

ログに記録してあとで見る方法

例えばPID=6661のfirefoxのCPU使用率を
100ms毎10秒間モニターして
ログに出力したい
なんて場合、こうします。

$ top -b -d 0.1 -n 100 | grep 6661 > firefox.txt

topコマンドのオプション -b:バッチモード
これを指定しないとファイル出力したとき
ぐちゃぐちゃになります

-d:更新間隔(秒)
0.1を指定すると100msになります
(組み込みLinuxだと1秒未満は指定できないみたい)

-n:回数
100を指定すると100回繰り返します

topコマンドの出力結果に対して
grepでPID=6661を抽出しfirefox.txtに出力します。
firefox.txtを開くとこんな感じに…。
9カラム目がCPU使用率になります。

ログをグラフにしてみた

LibreOfficeCalc(エクセルみたいなの)で開くときに
区切り文字がスペースのままだと列が揃わないため、
複数個のスペースをそれぞれカンマ1つに置換してCSVとしてオープンします。
するとこんな感じに。

(横軸が雑ですが…)グラフにするとこんな感じ。

③time:コマンドの実行時間を計測

実行例

$ time sleep 5
real 0m5.002s
user 0m0.000s
sys 0m0.002s

real プログラムの開始から終了までにかかった
実行時間(秒)
user ユーザランドの実行時間(秒)
sys カーネルモード・OSの実行時間(秒)

これを見て分かるのは
sleepしている5秒間はCPUを消費していないこと(ただ寝てる)。
sleepコマンドを実行するためにカーネルが2ms動作していること。

④strace:システムコールのスタックトレースを確認

straceを使ってみました。

インストール

straceは標準でインストールされていないことが多いので追加インストールします。
我が家の環境を表示しました。
Ubuntuなら同様の手順でインストールできます。

$ uname -a
Linux pavement1234 3.13.0-165-generic #215-Ubuntu SMP Wed Jan 16 11:46:47 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

インストール手順。

$sudo apt-get install strace

準備が出来たのでstraceを実行します。

-cオプション:
— count time, calls,
and errors for each syscall and report summary

各システムコールのレポートが表示されます。
これは超便利。

$ strace -c ./test
test
% time seconds usecs/call calls errors syscall
—— ———– ———– ——— ——— —————-
24.28 0.000486 69 7 mmap
15.98 0.000320 80 4 mprotect
10.34 0.000207 207 1 brk
9.34 0.000187 62 3 3 access
8.34 0.000167 167 1 munmap
8.19 0.000164 55 3 fstat
6.14 0.000123 62 2 open
5.94 0.000119 119 1 execve
5.54 0.000111 111 1 write
2.90 0.000058 29 2 close
2.40 0.000048 48 1 arch_prctl
0.60 0.000012 12 1 read
—— ———– ———– ——— ——— —————-
100.00 0.002002 27 3 total

-rオプション:
— print relative timestamp,
-t — absolute timestamp,
-tt — with usecs

-rは相対時間(マイクロ秒)。
個々の処理時間がわかるのでボトルネックを探しやすそう。

$ strace -r ./test
0.000000 execve(“./test”, [“./test”], [/* 22 vars */]) = 0
0.002786 brk(0) = 0xf88000
0.000495 access(“/etc/ld.so.nohwcap”, F_OK) = -1 ENOENT (No such file or directory)
0.000579 access(“/etc/ld.so.preload”, R_OK) = -1 ENOENT (No such file or directory)

-ttは絶対時間(マイクロ秒)。
これも便利。

$ strace -tt ./test
23:10:39.434994 execve(“./test”, [“./test”], [/* 22 vars */]) = 0
23:10:39.436785 brk(0) = 0x2364000
23:10:39.437300 access(“/etc/ld.so.nohwcap”, F_OK) = -1 ENOENT (No such file or directory)
23:10:39.437792 access(“/etc/ld.so.preload”, R_OK) = -1 ENOENT (No such file or directory)

-oオプション:
file — send trace output to FILE instead of stderr

straceログをファイルに出力。

$ strace -o test.log ./test

-pオプション:
pid — trace process with process id PID,
may be repeated

プロセスにアタッチしてシステムコールを監視できます。
Ctrl+Cでデタッチ。

$ ps
PID TTY TIME CMD
5412 pts/1 00:00:04 bash
7140 pts/1 00:00:00 ps

$ sudo strace -p 5412
Process 5412 attached
wait4(-1,
^CProcess 5412 detached
<detached …>

⑤キャッシュをクリアする方法

まず、現在のキャッシュ量を確認

$ free -m

total メモリ最大量
used 使用済メモリ量
free 使用可能メモリ量
shared 共有メモリ量
(古い機能らしく無視して良いみたい。
詳細不明。)
buffer バッファキャッシュ量(ブロックデバイス)
cached ページキャッシュ量(ファイルシステム)

次にバッファとキャッシュをクリア

$ sudo -s  キャッシュを消すときはルート権限になる必要あり
[sudo] password for pavement1234:
# echo 3 > /proc/sys/vm/drop_caches キャッシュをクリアしました

3を指定しておけば間違いないです。

1を指定 ページキャッシュをクリア
2を指定 Slabキャッシュをクリア
3を指定 ページキャッシュと
Slabキャッシュ両方をクリア

再びキャッシュ量を確認

# free -m

バッファとキャッシュがそれぞれ減りました。

プログラムの真の性能を見たいとき。
キャッシュに読み込み対象データがあると高速処理されてしまうため
必ずバッファとキャッシュをクリアした状態で確認しましょう。

⑥grep:文字列検索

文字列を検索する

例えば、こんなディレクトリ構成でこんなファイル配置で、
こんなファイル内容だったとします。

~/grepdir/a/a.txt (中身:pavement1234)
~/grepdir/b/b.txt (中身:pavement9876)
~/grepdir/c/c.txt (中身:PAVEMENT1234)
~/grepdir/d/d.txt (中身:pavement)
~/grepdir/e/e.txt (中身:1234)

grepdirにcdします。

$ cd grepdir

*./*に対してpavementという文字列を検索します。
するとこんな感じに表示されます。

$ grep pavement */*
a/a.txt:pavement1234
b/b.txt:pavement9876
d/d.txt:pavement

次に*/*に対してpavementという文字列を大文字・小文字の区別をせず検索します

すると大文字のPAVEMENTも検索に出てきます。

$ grep -i pavement */*
a/a.txt:pavement1234
b/b.txt:pavement9876
c/c.txt:PAVEMENT1234
d/d.txt:pavement

*/*に対してpavementという文字列を大文字・小文字の区別をせず検索し、検索結果に対して更に1234という文字列を検索します

するとこんな感じ。

$ grep -i pavement */* | grep 1234
a/a.txt:pavement1234
c/c.txt:PAVEMENT1234

いろいろなオプションを試してみる

-rはディレクトリを対象とする検索。

$ grep -r pavement *
a/a.txt:pavement1234
b/b.txt:pavement9876
d/d.txt:pavement

-vは不一致行を検索。

$ grep -v pavement */*
a/a.txt:
c/c.txt:PAVEMENT1234
d/d.txt:
e/e.txt:1234

-nは行Noを表示。

$ grep -n pavement */*
a/a.txt:1:pavement1234
b/b.txt:1:pavement9876
d/d.txt:1:pavement

-Eは拡張正規表現で検索。

$ grep -E ‘p|t’ */*
a/a.txt:pavement1234
b/b.txt:pavement9876
d/d.txt:pavement

-eは正規表現で検索。

$ grep -e p -e t */*
a/a.txt:pavement1234
b/b.txt:pavement9876
d/d.txt:pavement

-Lは該当しないファイルを検索。

$ grep -L pavement */*
c/c.txt
e/e.txt

⑦find:ファイル検索

Linuxファイルシステムの中はまるで迷宮のよう。
findコマンドを使いこなして、
目的のファイルを探せるようになりましょう。

まずfindのみ

例えば、
こんなディレクトリ構造で
こんなファイル配置になってるとします。

~/finddir/1.txt
~/finddir/a/a.txt
~/finddir/b/b.txt
~/finddir/c/c.txt
~/finddir/d/d.txt
~/finddir/e/e.txt

finddirにcdします。

$ cd ~/finddir

まずはfindだけ打ち込んでみました。
ドット”.”を含む全ディレクトリ、全ファイルが出力されました。

$ find
.
./c
./c/c.txt
./e
./e/e.txt
./a
./a/a.txt
1.txt
./d
./d/d.txt
./b
./b/b.txt

find ファイル名

指定したファイルだけが出力されました。

$ find /a/a.txt

./a/a.txt

find -name ファイル名
(ダブルクォーテーションを付けずワイルドカード指定)

シェルがワイルドカードをglob展開し、
a,b,c,d,eディレクトリ配下のtxtファイル全て
が出力されました。

$ find */*.txt

./a/a.txt
./b/b.txt
./c/c.txt
./d/d.txt
./e/e.txt

find -name ファイル名
(ダブルクォーテーションを付けてワイルドカード指定)

シエルによるワイルドカードのglob展開が抑制されfindコマンドが
ワイルドカードを展開
し、
finddirディレクトリ配下のtxtファイル全て
が出力されました(1.txtも含まれた)。

$ find -name “*.txt”
./c/c.txt
./e/e.txt
./a/a.txt
1.txt
./d/d.txt
./b/b.txt

find / -name ファイル名
(ダブルクォーテーションを付けてワイルドカード指定)

ルートディレクトリ配下の全てのtxtファイルを検索しますが、
アクセス権が無いファイルは”Permission denied”が表示されます。

$ find / -name “*.txt”
find: /var/spool/rsyslog': Permission denied
find:
/var/spool/cups’: Permission denied
find: /var/spool/cron/crontabs': Permission denied
find:
/var/lib/lightdm-data/lightdm’: Permission denied
find: /var/lib/lightdm': Permission denied
find:
/var/lib/udisks2′: Permission denied
/var/lib/anthy/depgraph/indepword.txt
find: /var/lib/sudo': Permission denied
find:
/var/lib/polkit-1′: Permission denied
/var/lib/nssdb/pkcs11.txt
find: `/var/cache/lightdm/dmrc’: Permission denied

エラーメッセージを抑制するには、
標準エラー出力を/dev/nullにリダイレクトする

すると「Permission denied」の行がなくなり
txtファイルのみ出力されました。

$ find / -name “*.txt” 2>/dev/null
/var/lib/anthy/depgraph/indepword.txt
/var/lib/nssdb/pkcs11.txt
/var/cache/dictionaries-common/ispell-dicts-list.txt

⑧プロセスのバックグラウンド実行

時間がかかるLinuxプロセスを
バックグラウンド実行したい。

プロセスをバックグラウンドで実行

コマンド入力の後にアンパサンド”&”を入れて実行すると、
プロセスIDを表示しプロンプトが復帰します。
これでPID=29956のプロセスが
バックグラウンド実行中となった訳です。

$ sleep 10 &
[1] 29956
$

しかし、
10秒待っても何も起こらないので適当にリターンキーを押すと、
いきなり完了メッセージが出ます。ちょっと使いにくい。

[1]+ Done sleep 10

10秒経ったら教えて欲しいと思う

ので、
バックグラウンド実行が始まったらwaitと入力して待ちます。
すると10秒後にdoneと完了メッセージが表示されます。

$ (sleep 10; echo done) &
[1] 15522
$ wait
done
[1]+  Done                    ( sleep 10; echo done )

次に、処理を3つ並列に実行

$ (sleep 10; echo done) &
[1] 27452
$ (sleep 10; echo done) &
[2] 27560
$ (sleep 10; echo done) &
[3] 27641

jobsと打つと、
3つとも実行中

$ jobs
[1]   Running                 ( sleep 5; echo done ) &
[2]-  Running                 ( sleep 5; echo done ) &
[3]+  Running                 ( sleep 5; echo done ) &

+ カレントジョブ
1つ前のジョブ
無印 2つ以上前のジョブ

waitと入力しとおくと、
doneと完了メッセージが続けて表示されます

$ wait
done
[1] Done ( sleep 10; echo done )
done
[2]- Done ( sleep 10; echo done )
done
[3]+ Done ( sleep 10; echo done )

⑨ドットコマンド(.)

5年ぐらい前に、どハマりしたドットコマンド。
カレントディレクトリを示す”.”をコマンドとして使うことについて、
当時知識がなくオマジナイのように使ってました。
今頃になってようやく利用方法がわかりましたのでメモしておきます。

スクリプトの中で定義したシェル変数がスクリプト終了後に表示できない

こんなシェル変数を定義するだけのシェルスクリプト
testvalue.sh“を作ります。

#!/bin/sh
TESTVALUE=testvalue

しかし”testvalue.sh”を実行し、
シェル変数を表示しようとしても何も出てきません

$ ./testvalue.sh
$ echo $TESTVALUE
(何も表示されない)

なぜシェル変数が表示されないのか

【理由】
シェルスクリプト実行開始時に子シェルが生成され、
子シェルの中でシェル変数を定義するものの、
シェルスクリプト終了時、
子シェル消滅と共にシェル変数も消滅するから

シェルスクリプト内で定義したシェル変数を
スクリプト終了後も使えるようにするにはどうすれば良いか

ちょっとわかりにくいですが、こうです
(./testvalue.shの前にドットコマンドを入力しています)

$ . ./testvalue.sh
$ echo $TESTVALUE
testvalue

こうすると、
シェルスクリプト内で定義したシェル変数が、
スクリプト実行後も使えます。

活用事例

複数のビルド環境を混在させており環境変数を極力汚したくない。
シェルスクリプトで沢山のシェル変数を定義し、
そのシェル変数を使ってビルドしたい
なんてときに便利です。
また、現在のシェルで手打ちしたシェル変数を、
シェルスクリプトの中で使いたいときも、ドットコマンドが使えます。
たとえば、こんな”echotestvalue.sh”を作ります。

#!/bin/sh
echo $TESTVALUE

で、
シェル変数TESTVALUEを事前に定義しておいて
echotestvalue.shを実行すると、
シェル変数の値testvalueが表示されます。

$ TESTVALUE=testvalue
$ . ./echotestvalue.sh
testvalue

備考

bashにおいて、sourceコマンドとドットコマンド「.」は同じ意味らしい。
しかし、bashの前身であるsh(化石)では
ドットコマンド「.」しか効かないことがあったらしい。

⑩Linuxデスクトップのスクリーンキャプチャー

WindowsだとPrintScreenボタンで画面全体、
Alt+PrintScreenで選択したウィンドウのスクリーンキャプチャを
撮ることができますが、Linuxではどうすれば良いのか。

いろいろなやり方がある

全体を撮りたい場合はこう。

gnome-screenshot

エリアを選択したい場合はこう。

gnome-screenshot –area

Windowを選択したい場合はこう。

gnome-screenshot –window

5秒後までにWindowを選択したい場合はこう。

gnome-screenshot –window –delay 5

カメラっぽい

スクリーンキャプチャの瞬間、
カメラのシャッターが押されたような撮影してる感じがあり、かっこいい。
Windowsもやればいいのに。

⑪リダイレクト(2>&1)の意味

さて、これは何でしょう。

2>&1

【答え】
標準エラー出力【2】を標準出力【1】に出力する
(=設定を同じにする)

なぜこんなものが必要なのか

例えば”test.sh”というこんなスクリプトを作ります。

#!/bin/sh

echo “bububu”
ls /gagaga #存在しないファイル名

単に実行するとこんな感じに出力されますが、、

$ ./test.sh
bububu
ls: cannot access /gagaga: No such file or directory

ファイルにリダイレクトすると”bububu”はファイル出力されるものの
”… No such file or directory”はファイルには出力されず
標準エラー出力【2】に出ちゃいます。
スクリプトの結果をすべてファイルに出力したい場合に不便です。
そこで、2>&1の出番ですね。

$ ./test.sh > log.txt 2>&1

こうすると、
”bububu”も
”… No such file or directory”も
両方ファイルに出力されます(取りこぼしなし)。

応用編

エラーメッセージがちょろちょろ出て鬱陶しいプログラムがある場合、
リダイレクト先を/dev/null(ビットバケツ)にすると
何も出力されなくなって快適です。
(エラーを知りたい場合はやっちゃだめ)。

$ ./test.sh > /dev/null 2>&1

まとめ

Linuxコマンドの便利な小技たちをご紹介しました。

ABOUT ME
ペイヴメント
ペイヴメントのエンジニア塾(当ブログ)では20年以上の経験から得られたプログラミング系ノウハウについてベテランにも満足して頂けるような内容の濃いコンテンツを初心者にも分かりやすい形で日々発信しています。【経歴】ベンチャーのソフトハウスで4年勤務後、精密機器メーカーのソフト開発部門に勤務し今に至ります。
error: Content is protected !!