リダイレクトとパイプ

これも忘れそうなのでメモ。

標準入力から標準出力と標準エラー出力

リダイレクトで使うのは、標準入出力。
イメージ湧かせてみる。

stdin/stdout/stderrには0/1/2ってファイルディスクリプタがついてる。
キーボードが標準入力で、 画面出力が標準出力と標準エラー出力。

                                                +------------+
+----------+    +-----------+    +---------+ -> | 1 - stdout |
| keyboard | -> | 0 - stdin | -> | command |    +------------+
+----------+    +-----------+    |         |    +------------+
                                 +---------+ -> | 2 - stderr |
                                                +------------+

ゼロから入力されて、1と2に出力される。

標準入出力のファイルディスクリプタ

file pointernum
標準入力stdin0
標準出力stdout1
標準エラー出力stderr2

ゼロから2まで3つある。

c言語でファイルをオープンすると、numの箇所は0/1/2が利用済みなので3から始まる。

入力してみる

mariadbとかmysqlはデータベースのダンプがテキストファイルになってて、ベタっとテーブル作成・挿入のsqlが入ってる。

例えば、最初のコメント行が終わったあたりの一部はこんな感じでcreate database/create table/lock table/insert intoが入ってる。

CREATE DATABASE /*!32312 IF NOT EXISTS*/ `nariDB_Django` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */;

USE `nariDB_Django`;

--
-- Table structure for table `GVIS_keihi`
--

DROP TABLE IF EXISTS `GVIS_keihi`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `GVIS_keihi` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `workPeriod` datetime DEFAULT NULL COMMENT '業務期間-開始日',
  `workShubetsu` char(4) DEFAULT NULL COMMENT '経費種別',
:(中略)
LOCK TABLES `GVIS_keihi` WRITE;
/*!40000 ALTER TABLE `GVIS_keihi` DISABLE KEYS */;
INSERT INTO `GVIS_keihi` VALUES
(1,'2014-03-01 00:00:00','1yan',39,'2014-03-01 00:00:00','024-地代家賃','家賃',0.0000,'本来>は6万円だが売上ゼロのためゼロ',NULL,NULL,'2015-01-09 17:12:34','gvis'),
(2,'2014-03-01 00:00:00','1yan',40,'2014-03-01 00:00:00','020-水道光熱費','光熱費',0.0000,'>本来は1万円だが売上ゼロのためゼロ',NULL,NULL,'2015-01-09 17:12:58','gvis'),
(3,'2014-03-01 00:00:00','2kgc',42,'2014-03-18 00:00:00','003-研究開発費','IT技術研究費',100.0000,NULL,NULL,NULL,NULL,NULL),
:(中略)

このSQLをコマンドラインでmysqlに渡すとダンプファイルを作ったときのデータベースをリストアしてくれる。

自分はシェルスクリプトに書いて使ってて、実際にやってる箇所だけ抜粋するとリダイレクトで入力させてる。

/usr/bin/mysql -u root -p${DBPASS}  < /etc/mysql/conf.d/nari/fullback/FullBackup_nariDB_Django.sql

sqlファイルはだいたい1.3GBぐらいで7万行弱あるけど、実際に入力させてデータベースをリストアするのにかかる時間は2分以内。

dockerのコンテナでもminikubeのPodでも同じぐらいで終わる。

出力してみる

標準出力

普通に出力。何も書いてないから、「aa」が標準出力に出る。

nari@gvis-mac ~ % echo aa
aa
nari@gvis-mac ~ % 

標準出力に出したものをファイルに書き出して、それをcatしてみる。
標準出力へは「1>」で出力先を指定する。

nari@gvis-mac ~ % echo aa 1> aa.txt ; cat aa.txt     
aa
nari@gvis-mac ~ % 

さっきの続きで、存在しないファイルをlsするとエラーが表示される。

nari@gvis-mac ~ % ls aa.txt ; ls bb.txt 
aa.txt
ls: bb.txt: No such file or directory
nari@gvis-mac 

エラーメッセージはエラー出力に出てるはずなので、その結果を「2>」でリダイレクトしてファイルに出力させる。

nari@gvis-mac ~ % ls aa.txt ; ls bb.txt 2> bb.err.txt 
aa.txt
nari@gvis-mac ~ % cat bb.err.txt 
ls: bb.txt: No such file or directory
nari@gvis-mac ~ % 

標準エラー出力

画面に標準出力と標準エラー出力が一緒に出てしまうので、簡単なc言語のプログラムを用意。

write.cってファイルを用意。
めっちゃ簡単な内容で、fprintfで標準出力と、標準エラー出力に1行ずつ書ける。
最後にprintfで標準出力にだけ行出力してる。

nari@gvis-mac gvis-stdout % cat write.c
#include <stdio.h>

int main(int argc, char **argv) {
  fprintf(stdout, "---stdout gvis---\n");
  fprintf(stderr, "---stderr gvis---\n");
  printf("---end---\n");
}

コンパイルして動かすと3行が画面出力される。

nari@gvis-mac gvis-stdout % gcc write.c -o write
nari@gvis-mac gvis-stdout % ./write 
---stdout gvis---
---stderr gvis---
---end---
nari@gvis-mac gvis-stdout % 

単純にコマンドラインからリダイレクトするとどうなるのか。

nari@gvis-mac gvis-stdout % ./write > out.txt 
---stderr gvis---
nari@gvis-mac gvis-stdout % cat out.txt 
---stdout gvis---
---end---
nari@gvis-mac gvis-stdout % 

標準出力はout.txtにリダイレクトしてるから、画面にはstderrへfprintfしてる箇所だけが表示される。

out.txtにはリダイレクトした標準出力の2行が入ってる。

画面に表示されなかったのを表示させるにはteeを使う。

nari@gvis-mac gvis-stdout % ./write > out.txt | tee 
---stderr gvis---
---stdout gvis---
---end---
nari@gvis-mac gvis-stdout % cat out.txt 
---stdout gvis---
---end---
nari@gvis-mac gvis-stdout %

分割いけますか

標準出力と、標準エラー出力で分割してファイル出力できるかどうか。

nari@gvis-mac gvis-stdout % ./write 1>stdout.txt 2> stderr.txt 
nari@gvis-mac gvis-stdout % cat stdout.txt 
---stdout gvis---
---end---
nari@gvis-mac gvis-stdout % cat stderr.txt 
---stderr gvis---
nari@gvis-mac gvis-stdout % 

さっきの1行目と3行目がstdout.txtに出力され、2行目だけがstderr.txtに出力されてるから、ちゃんとできてる。

くっつけられますか

標準出力と標準エラー出力をくっつけて出力できるか。

nari@gvis-mac gvis-stdout % ./write > concat.txt 2>&1
nari@gvis-mac gvis-stdout % cat concat.txt 
---stderr gvis---
---stdout gvis---
---end---
nari@gvis-mac gvis-stdout % 

「2>&1」ってやると、標準エラー出力を標準出力と同じにして、concat.txtに出力するから、何も表示されない。

concat.txtをcatすると、標準エラー出力・標準出力の順でくっついた出力が見える。

ログにも画面にも出力させるときは、これまたteeを使う。

nari@gvis-mac gvis-stdout % ./write > concat.txt 2>&1 | tee 
---stderr gvis---
---stdout gvis---
---end---
nari@gvis-mac gvis-stdout % cat concat.txt 
---stderr gvis---
---stdout gvis---
---end---
nari@gvis-mac gvis-stdout %

ちょっと思いつき

んじゃ、「2>&1」の「2」と「1」をひっくり返して「1>&2」ってするとどうなるんか? エラーになってくれるんか?

nari@gvis-mac gvis-stdout % ./write > concat.txt 1>&2
---stderr gvis---
---stdout gvis---
---end---
nari@gvis-mac gvis-stdout % cat concat.txt 
---stdout gvis---
---end---
nari@gvis-mac gvis-stdout % 

画面出力は「2>&1」も「1>&2」も同じで、ファイルは標準出力だけになる。

画面出力は標準エラー出力が表示されてから標準出力の内容が表示されるのは一緒。

標準エラー出力を、標準出力の後にファイル出力させるならこう書いたらええんかな。

nari@gvis-mac gvis-stdout % ./write 1>stdout.txt 2> stderr.txt ; cat stdout.txt stderr.txt 
---stdout gvis---
---end---
---stderr gvis---
nari@gvis-mac gvis-stdout % 

別々に出力してから、自分の思うようにcatの引数にファイル名を書いたらええみたい。

dockerコンテナのログ出力

コンテナ仮想化でログ出力するとき、/proc/1/fd/1へリダイレクトしたらええらしい。

たまたまログ出力方法を探してて見つけた。作者さんありがとう。

実際にやってみたら、そうやった。

nari@nafslinux-ubu22:~$ docker exec -it docker-sv_django-1 bash 
root@svdjango:/# 
root@svdjango:/# echo gvis-log1 > /proc/1/fd/1 
root@svdjango:/# echo gvis-log2 > /proc/1/fd/1 
root@svdjango:/# echo gvis-log3 > /proc/1/fd/1 
root@svdjango:/# exit
exit
nari@nafslinux-ubu22:~$
nari@nafslinux-ubu22:~$ docker logs docker-sv_django-1
 :(中略)
2023-09-08 15:23:30,883 CRIT Supervisor is running as root.  Privileges were not dropped because no user is specified in the config file.  If you intend to run as root, you can set user=root in the config file to avoid this message.
2023-09-08 15:23:30,884 INFO Included extra file "/etc/supervisor/conf.d/supervisor-app.conf" during parsing
2023-09-08 15:23:31,193 INFO RPC interface 'supervisor' initialized
2023-09-08 15:23:31,193 CRIT Server 'unix_http_server' running without any HTTP authentication checking
2023-09-08 15:23:31,194 INFO supervisord started with pid 1
2023-09-08 15:23:32,199 INFO spawned: 'app-uwsgi' with pid 7
2023-09-08 15:23:32,207 INFO spawned: 'nginx-app' with pid 8
2023-09-08 15:23:34,132 INFO success: app-uwsgi entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2023-09-08 15:23:34,132 INFO success: nginx-app entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
gvis-log1
gvis-log2
gvis-log3

kubernetesでも同じ?

やってみた。

nari@gvis-mac script % kubectl exec -it `kubectl get pod | grep sv-django | awk '{print $1}'` -- bash
root@sv-django:/# echo gvis-log1 > /proc/1/fd/1
root@sv-django:/# echo gvis-log2 > /proc/1/fd/1
root@sv-django:/# echo gvis-log3 > /proc/1/fd/1
root@sv-django:/# exit
exit
nari@gvis-mac script % kubectl logs `kubectl get pod | grep sv-django | awk '{print $1}'`
 :(中略)
2023-09-08 16:21:47,057 CRIT Supervisor is running as root.  Privileges were not dropped because no user is specified in the config file.  If you intend to run as root, you can set user=root in the config file to avoid this message.
2023-09-08 16:21:47,057 INFO Included extra file "/etc/supervisor/conf.d/supervisor-app.conf" during parsing
2023-09-08 16:21:47,070 INFO RPC interface 'supervisor' initialized
2023-09-08 16:21:47,070 CRIT Server 'unix_http_server' running without any HTTP authentication checking
2023-09-08 16:21:47,071 INFO supervisord started with pid 1
2023-09-08 16:21:48,080 INFO spawned: 'app-uwsgi' with pid 7
2023-09-08 16:21:48,092 INFO spawned: 'nginx-app' with pid 8
2023-09-08 16:21:49,242 INFO success: app-uwsgi entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2023-09-08 16:21:49,242 INFO success: nginx-app entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
gvis-log1
gvis-log2
gvis-log3
nari@gvis-mac script % 

ここでdockerでもkubernetesでも/proc/1/fd/1というのは、「PID1のプロセスの標準出力」なのね。
コンテナ仮想化してて、監視で特定のキーワード拾いたいときにこういうのが使えるかも。

ログは肥大化するし、ローテート面倒やし、データベースに吐いたほうがええとは思うけど・・・。

実行結果を使ってパイプ処理させる

パイプ処理ってのは実行結果を渡してく連結のこと。

+---------+    +-----------+    +---------+
| command | -> |   stdin   | -> | command |
+---------+    +-----------+    +---------+

ls使った例

普段使ってる感じやと、lsコマンドの結果が標準出力に出て、さらにその結果をgrepさせてる。

nari@nafslinux-ubu22:~$ ls -l / | grep '\->'
lrwxrwxrwx   1 root root     7  5月  8  2022 bin -> usr/bin
lrwxrwxrwx   1 root root     7  5月  8  2022 lib -> usr/lib
lrwxrwxrwx   1 root root     9  5月  8  2022 lib32 -> usr/lib32
lrwxrwxrwx   1 root root     9  5月  8  2022 lib64 -> usr/lib64
lrwxrwxrwx   1 root root    10  5月  8  2022 libx32 -> usr/libx32
lrwxrwxrwx   1 root root     8  5月  8  2022 sbin -> usr/sbin
nari@nafslinux-ubu22:~$ 

ubuntu22のルートフォルダをlsで結果取得し、さらにそこからシンボリックリンクがついてるものとして「->」をgrepしてる。
「grep」を「grev -v」にしたら、シンボリックリンクがついてないものをgrepで探せる。

普段使ってる例

djangoのモジュールをバージョンアップするときに日常的にやってること。

自分のdjangoはコンテナで動かしてて、ベースイメージのubuntuにpythonをapt-getしてインストールしてる。

さらにpythonにいくつかモジュールをインストールしてて、pip3を使ってdjangoとかnumpyとかを入れて使ってる。

2〜3ヶ月に1回ぐらいはバージョンアップ対象があるかどうかを確認して確認してのがこんな感じ。

nari@nafslinux-ubu22:/docker$ docker exec -it docker-sv_django-1 bash ⭐️コンテナにログイン
root@svdjango:/# pip3 list -o 
Package    Version Latest Type
---------- ------- ------ -----
setuptools 68.1.2  68.2.0 wheel
root@svdjango:/# 

ここではsetuptoolsってモジュールが新しいバージョン出てる。
実行結果の3行目にある1つ目のフィールドがパッケージ名なので、それを切り出す。

root@svdjango:/# pip3 list -o | tail -n +3 | awk '{ print $1 }' 
setuptools
root@svdjango:/# 

このモジュール名を引数にして、例えばこんなふうにバージョンアップ処理を動かしたい。

# pip3 install --upgrade setuptools

パイプでパッケージ名をxargsに渡して、pip3を動かしてみる。

root@svdjango:/# pip3 list -o | tail -n +3 | awk '{ print $1 }' | xargs pip3 install --upgrade
Requirement already satisfied: setuptools in /usr/local/lib/python3.10/dist-packages (68.1.2)
Collecting setuptools
  Obtaining dependency information for setuptools from https://files.pythonhosted.org/packages/82/3b/0715493246eb08e93506f4da0efe1d05a3c9d9ac3b76e97cc362890e6adf/setuptools-68.2.0-py3-none-any.whl.metadata
  Downloading setuptools-68.2.0-py3-none-any.whl.metadata (6.3 kB)
Downloading setuptools-68.2.0-py3-none-any.whl (807 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 807.8/807.8 kB 8.3 MB/s eta 0:00:00
Installing collected packages: setuptools
  Attempting uninstall: setuptools
    Found existing installation: setuptools 68.1.2
    Uninstalling setuptools-68.1.2:
      Successfully uninstalled setuptools-68.1.2
Successfully installed setuptools-68.2.0
 :(中略)
root@svdjango:/# 

1つだけなら動作確認めっちゃ簡単で、pip list -oの実行結果がいくつもあるときがこのコマンドラインが役に立つ時。

いくつもある瞬間はそうそうないけど、多い時はそれぐらいは出てくるからアップデートを一気にやりたいとき便利。

ただし、pythonはアップデートがゆっくりなモジュールもあるから、あんまり上げすぎると不整合が起きてまうから、そのときはいったんコンテナ潰してdockerビルドやり直せばええ。

コメント

タイトルとURLをコピーしました