アナグマのモノローグ https://monologu.com 現役SEのおぼえがき Sat, 11 Feb 2023 06:42:54 +0000 ja hourly 1 https://wordpress.org/?v=6.0.2 nginxでWebサーバーを構築する(Ubuntu編)- マルチドメイン・HTTPS化の設定も解説 https://monologu.com/nginx-ubuntu/ Sat, 11 Feb 2023 05:00:21 +0000 https://monologu.com/?p=1144 Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
nginxは、Webサーバーやリバースプロキシに使われるソフトウェアの一つです。

WebサーバーといえばApache HTTP Serverを思い浮かべる人も多いと思いますが、近年ではnginxも非常に人気があります。事実、Webサーバーのシェアにおいて国内のみならず世界的にみてもApacheを抜いてnginxが1位を獲得しているとの調査結果もあります。

人気の理由はApacheと比較しても性能が高く、リソースの消費も少ないことが挙げられます。nginxの人気はこの先も当分続くでしょう。

この記事では、今最も人気のあるnginxでWebサーバーを構築する方法を説明します。また単純な構築方法以外に、次のことについても設定方法を解説します。

  • マルチドメイン(バーチャルホスト)対応
  • HTTPS(SSL化)対応

なおOSはUbuntuの利用を前提としています。

nginxでWebサーバーを構築する

nginxのインストール

nginxのパッケージは、Ubuntuのリポジトリで提供されていますので、aptコマンドを使ってインストールします。

パッケージをインストールする前に、まずパッケージ一覧を更新します。

$ sudo apt update

nginxパッケージをインストールします。

$ sudo apt install nginx

nginxの起動

nginxが自動起動されるように設定がされているか確認します。

$ sudo systemctl is-enabled nginx
enabled

「enabled」と表示されれば、OS起動時に自動的にnginxが開始されます。

次にnginxが起動しているか確認します。

$ sudo systemctl status nginx

「Active: active (running)」と表示されれば、nginxは起動しています。

ファイヤーウォールの設定

ファイヤーウォールを設定します。WebサーバーなのでHTTPの80番ポートとHTTPSの443番ポートをファイヤーウォールで許可します。

まずは現状のファイヤーウォールの設定を確認します。

$ sudo ufw status

「Status: active」と表示されていれば起動しています。ファイヤーウォールが起動していない場合(「Status: inactive」と表示された場合)、次のコマンドで起動します。

$ sudo ufw enable

デフォルトでは次のようにファイヤーウォールで何も許可されていないと思います。

$ sudo ufw status
Status: active

80番ポート(HTTP)と443番ポート(HTTPS)の通信を許可するように設定します。まずは、ufwのアプリケーションプロファイルのリストを確認します。

$ sudo ufw app list
Available applications:
  Nginx Full
  Nginx HTTP
  Nginx HTTPS
  OpenSSH

次の3つのプロファイルはNginxをインストールで追加されたものです。

  Nginx Full
  Nginx HTTP
  Nginx HTTPS

「Nginx Full」を許可するとTCPの80番、443番ポートが開かれ、「Nginx HTTP」を許可するとTCPの80番ポート、「Nginx HTTPS」はTCPの443番ポートだけが開放されます。

プロファイルの情報は次のコマンドで確認できます。

$ sudo ufw app info 'Nginx Full'
Profile: Nginx Full
Title: Web Server (Nginx, HTTP + HTTPS)
Description: Small, but very powerful and efficient web server

Ports:
  80,443/tcp

ここでは80番、443番ポートを開放するので「Nginx Full」を許可します。ついでに22番ポート(SSH)についても許可します。

$ sudo ufw allow 'Nginx Full'
$ sudo ufw allow OpenSSH

許可後の設定を確認します。

$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere                  
Nginx Full                 ALLOW       Anywhere                  
OpenSSH (v6)               ALLOW       Anywhere (v6)            
Nginx Full (v6)            ALLOW       Anywhere (v6)

変更した内容を読み込むためにファイヤーウォールをリロードを実施します。

$ sudo ufw reload
Firewall reloaded

Webサーバーの動作確認

ここまでの設定が完了すれば、Webサーバーとして動作しているはずですので、正常に動作しているか確認します。

ウェブブラウザで次のURLでアクセスしてみましょう。

http://IPアドレスまたはFQDN/

次のようにnginxのサンプルページが表示されれば正常に稼働しています。

nginxのサンプルページ

設定ファイルの確認

nginxの設定ファイルを覗いてみましょう。nginxの主な設定ファイルは次の2つです。

/etc/nginx/nginx.conf
/etc/nginx/sites-available/default

おそらくですが、Ubuntuの設定ファイルの構造は少し特殊です。

serverディレクティブは、nginx.confファイルに直接記述されているのが普通だと思われますが、Ubuntuの場合は/etc/nginx/sites-availableディレクトリの個別ファイルに記述されています。

そして次のように/etc/nginx/sites-enabledに、このファイルへのリンクが張られています。

$ ls -l /etc/nginx/sites-enabled
total 0
lrwxrwxrwx 1 root root 34 Feb  6 23:49 default -> /etc/nginx/sites-available/default

/etc/nginx/sites-enabledディレクトリのファイルは、nginx.confで次のように読み込む設定がされていますので、結果的にリンクを通して/etc/nginx/sites-available/defaultが読み込まれます。

include /etc/nginx/sites-enabled/*;

通常は「nginx missing sites-available directory」に記載があるとおり、このような構造になっていないようです。

ただし、この設定ファイルの構造は次に説明するバーチャルホストを追加する場合に便利です。

Webページの公開

あとはHTMLファイルなどを配置すればWebページを公開できます。

ドキュメントルートの設定は/etc/nginx/sites-available/defaultファイルに記載されています。

root /var/www/html;

/var/www/htmlディレクトリの下に公開するWebページを配置しましょう。

このあとは、マルチドメイン(バーチャルホスト)やHTTPS(SSL化)について説明します。

マルチドメイン・バーチャルホストの設定

マルチドメインもバーチャルホストも、大体同じ意味で使われることが多いです。

マルチドメインは複数のドメインを1つのサーバでホストすることを意味します。例えば「http://foo.example.com/path/to/file」というULRでアクセスがあった場合は、foo.example.comドメイン用の/path/to/fileというファイルを返しますが、「http://bar.example.com/path/to/file」というULRでアクセスがあった場合は、bar.example.comドメイン用の/path/to/fileを返すことができます。

これはユーザーからすると「foo.example.com」をホストするWebサーバーと「bar.example.com」をホストするWebサーバーが別々に存在するように見えます。しかし、実際には1つのWebサーバーが仮想的に2つのWebサーバーがあるように振る舞っているだけです。この仮想的なWebサーバーのことをバーチャルホストと呼びます。

それでは実際にバーチャルホストの設定を追加しましょう。ここでは「foo.example.com」と「bar.example.com」の2つのドメインをホストするように設定していきます。

/etc/nginx/sites-available/defaultにバーチャルホストの設定例がありますので、これを参考に進めて行きます。

# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#
#server {
#	listen 80;
#	listen [::]:80;
#
#	server_name example.com;
#
#	root /var/www/example.com;
#	index index.html;
#
#	location / {
#		try_files $uri $uri/ =404;
#	}
#}

各ドメインのドキュメントルートディレクトリを作成する

最初は各ドメイン用のドキュメントルートディレクトリを作成します。

$ sudo mkdir /var/www/foo.example.com /var/www/bar.example.com

ディレクトリはドメイン名で作成します。

各ドメイン用のWebページはこれらのディレクトリに配置しますので、それらを配置するできるように適切な所有者とグループ所有者、および権限を必要であれば設定します。

$ sudo chown 所有者:グループ所有者 /var/www/foo.example.com /var/www/bar.example.com
$ sudo chmod 755 /var/www/foo.example.com /var/www/bar.example.com

バーチャルホストの設定ファイルを作成する

次にそれぞれのドメイン用にserverディレクティブを記述したファイルを、/etc/nginx/sites-availableディレクトリに作成します。

最初にfoo.example.com用のファイルを作成します。

$ sudo vi /etc/nginx/sites-available/foo.example.com

記述する内容は次のとおりです。「server_name」にはドメイン名を、「root」には先ほど作成したディレクトリを設定します。

server {
    listen 80;
    listen [::]:80;

    server_name foo.example.com;

    root /var/www/foo.example.com;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

同様に「bar.example.com」用のファイルを作成します。

$ sudo vi /etc/nginx/sites-available/bar.example.com

記述する内容は次のとおりです。

server {
    listen 80;
    listen [::]:80;

    server_name bar.example.com;

    root /var/www/bar.example.com;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

リンクを作成する

/etc/nginx/sites-availableにファイル作成しただけでは、このファイルは読み込まれません。このファイルが読み込まれるように/etc/nginx/sites-availableディレクトリにリンクを作成します。

$ sudo ln -s /etc/nginx/sites-available/foo.example.com /etc/nginx/sites-enabled/
$ sudo ln -s /etc/nginx/sites-available/bar.example.com /etc/nginx/sites-enabled/

設定ファイルを確認する

作成した設定ファイルに構文の誤りがないかチェックします。

$ sudo nginx -t

-tオプションは設定ファイルをテストし、エラーがあればそれを表示します。

$ sudo nginx -T

-Tオプションも設定ファイルをテストし、設定ファイルの内容を表示します。includeされたファイルは展開されて表示されます。

設定ファイルにエラーがないこと、および先ほど作成したファイルがincludeされていることを確認します。

nginxの再起動

nginxを再起動して設定を読み込みます。

$ sudo systemctl restart nginx

バーチャルホストのテスト

実際にバーチャルホストの設定が動作するかテストしてみましょう。

まずはテスト用に次の2つのファイルを用意します。

/var/www/foo.example.com/index.html
/var/www/bar.example.com/index.html

index.htmlの中身は単なるテキストで構いません。例えば、次の内容を記述します。

This is foo.exmple.com site.

ファイルが用意できたらブラウザから次のURLでアクセスします。

http://foo.example.com/
http://bar.example.com/

用意したテキストが表示されれば正常に動作しています。

ただし、上記のようにドメイン名でアクセスするにはDNSを設定するなどドメイン名を名前解決できるようにしておく必要があります。代わりにcurlコマンドで簡易的に確認することもできます。それには次のように実行します。

$ curl -H "Host: foo.example.com" http://localhost/
This is foo.exmple.com site.
$ curl -H "Host: bar.example.com" http://localhost/
This is bar.exmple.com site.

HTTPSに対応する

昨今ではHTTPSに対応することが当たり前のようになってきています。nginxで建てたWebサーバーもHTTPSに対応するようにしましょう。

HTTPSで通信できるようにするにはSSLサーバー証明書が必要になります。サーバー証明書はCA(認証局)が発行するもので、有償・無償のものがあります。

ここではLet's Encryptのサーバー証明書を利用します。Let's Encryptは無料で利用でき、レンタルサーバーなどでも利用も多いです。

Let's Encryptのサーバー証明書を利用するときには、Certbotという無料ツールを使うのが一般的です。Certbotを使うとサーバー証明書の取得からサーバーへの配備、そしてサーバー証明書の自動更新まで簡単にできるようになします。

サーバー証明書に有効期限があり、期限が満了する前に更新する必要があります。Let's Encryptの発行するサーバー証明書の有効期限は90日です。

Cerbotパッケージのインストール

まずはCerbotとそれに関連したパッケージをインストールします。

$ sudo apt install certbot python3-certbot-nginx

次のコマンドで、サーバー証明書の取得からサーバーへの配備、およびHTTPSアクセスできるように設定が変更されます。証明書が必要なドメイン名は-dオプションに指定します。今回はマルチドメインなので-dを複数指定しています。

sudo certbot --nginx -d foo.example.com -d bar.example.com

コマンドは対話形式で実行され、いくつか入力が求められますので適切な回答を入力していきます。

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator nginx, Installer nginx
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel): <メールアドレスを入力>

emailアドレスを入力します。このアドレスは緊急な更新やセキュリティ通知のために使われます。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf. You must
agree in order to register with the ACME server at
https://acme-v02.api.letsencrypt.org/directory
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(A)gree/(C)ancel: A

利用規約に同意するかと言っているので、Aを入力して同意します。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about our work
encrypting the web, EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: N

メールアドレスを共有して通知を受け取れるようにするか?というような内容です。必要ないのでNを入力する。

Obtaining a new certificate
Performing the following challenges:
http-01 challenge for foo.exmple.com
http-01 challenge for bar.exmple.com
Waiting for verification...
Cleaning up challenges
Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/foo.exmple.com
Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/bar.exmple.com

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2

「1: No redirect」はこれ以上設定を変更しない。「2: Redirect」はすべてのリクエストをHTTPSアクセスにリダイレクトする設定に変更します。HTTPへのアクセスはHTTPSへリダイレクトしたいので「2」を入力します。

Redirecting all traffic on port 80 to ssl in /etc/nginx/sites-enabled/foo.exmple.com
Redirecting all traffic on port 80 to ssl in /etc/nginx/sites-enabled/bar.exmple.com

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully enabled https://foo.exmple.com and
https://bar.exmple.com

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=foo.exmple.com
https://www.ssllabs.com/ssltest/analyze.html?d=bar.exmple.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

/etc/nginx/sites-enabledディレクトリは以下のファイルでHTTPをHTTPSへリダイレクトする設定に変更した旨のメッセージと、HTTP化が成功した旨のメッセージが表示されていれば問題ありません。また、テストするためのURLも表示されています。

IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/foo.example.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/foo.example.com/privkey.pem
Your cert will expire on 2023-05-08. To obtain a new or tweaked
version of this certificate in the future, simply run certbot again
with the "certonly" option. To non-interactively renew *all* of
your certificates, run "certbot renew"
- Your account credentials have been saved in your Certbot
configuration directory at /etc/letsencrypt. You should make a
secure backup of this folder now. This configuration directory will
also contain certificates and private keys obtained by Certbot so
making regular backups of this folder is ideal.
- If you like Certbot, please consider supporting our work by:

Donating to ISRG /

証明書の保存先や秘密鍵の保存先が表示されます。そのほか今回取得した証明書の満了期限や更新方法も表示されています。また、アカウントの認証情報などが/etc/letsencryptディレクトリに保存されているので、定期的のこのディレクトリをバックアップすることが推奨されるとのことです。

HTTPSアクセスの動作確認

ここまでが完了したらHTTPSでアクセスできるようになっています。HTTPSでアクセスして正しくページが表示されることを確認しましょう。

https://foo.example.com/
https://bar.example.com/

また、HTTPへのアクセスがHTTPSへリダイレクトされることも確認します。

http://foo.example.com/
http://bar.example.com/

サーバー証明書の自動更新テスト

Let’s Encryptのサーバー証明書の有効期限は90日です。そのため有効期限が満了する前に証明書を更新する必要があります。

Ubuntuの場合、systemdのタイマーとして証明書の自動更新が既に設定されているので何かをする必要はありません。設定されているタイマーは次のコマンドで確認できます。

$ sudo systemctl status certbot.timer
$ sudo systemctl list-timers | grep certbot

また、次のコマンドを実行すると自動更新のテストが実施できます。

$ sudo certbot renew --dry-run

成功した旨が表示されれば問題ありません。

Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
sudoコマンドの使い方 https://monologu.com/sudo-command/ Sun, 05 Feb 2023 10:12:07 +0000 https://monologu.com/?p=1134 Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
sudoコマンドは、指定したユーザー権限で特定のコマンドを実行するためのコマンドです。

主にスーパーユーザー権限を必要とするコマンドを一般ユーザーが実行できるようにするために使われます。

sudoコマンドを利用する目的

複数人で管理しているマシンでスーパーユーザー権限を必要とするコマンドを実行するには、suコマンドでスーパーユーザーにスイッチしてからコマンドを実行する運用もできますが、この場合、全ての管理者でスーパーユーザーのパスワードを共有する必要があります。これはセキュリティ的にもよろしくありません。

sudoコマンドを利用すると、一般ユーザーがスーパーユーザー権限を必要とするコマンドを実行できるようになります。これによってスーパーユーザーのパスワードを共有する必要がなくなるのでセキュリティも向上します。

また、sudoコマンドの実行はログファイルに記録されますので、どのユーザーが特権コマンドを実行したかを追跡できるようにもなります。

sudoコマンドの設定

sudoコマンドを利用するには/etc/sudoersファイルに設定が必要です。

visudoコマンド

/etc/sudoersファイルを編集するには、rootユーザーでvisudoコマンドを実行します。

# visudo

visudoコマンドを実行すると通常はviエディタで/etc/sudoersファイルが開かれます。

/etc/sudoersファイル

特定のユーザー(やグループ)にsudoコマンドの実行を許可する主な設定は次のとおりです。

ユーザー名 ホスト名=(実行ユーザー名) コマンド
%グループ名 ホスト名=(実行ユーザー名) コマンド
ユーザー名、%グループ名
コマンドの実行を許可する「ユーザー名」か「%グループ名」、あるいは「ALL」を指定します。
ホスト名
実行を許可する「ホスト名」、「IPアドレス」、あるいは「ALL」を指定します。
実行ユーザー名
コマンド実行時のユーザー名(省略時はrootと仮定される)、あるいは「ALL」を指定します。
コマンド
実行を許可する「コマンドのパス」、あるいは「ALL」を指定します。

設定例

ディストリビューションによっては、あらかじめ次のような設定が記述されている場合があります。

%wheel	ALL=(ALL)	ALL

この設定でwheelグループに所属するユーザーは全てのコマンドの実行が許可されます。

ユーザーをwheelグループに所属させるには次のように実行します。

# id taro
uid=1001(taro) gid=1001(taro) groups=1001(taro)
# usermod -aG wheel taro
# id taro
uid=1001(taro) gid=1001(taro) groups=1001(taro),10(wheel)

次にtaroユーザーにshutdownコマンドを実行できるようにしてみましょう。それには次の行を追加します。

taro  ALL=(ALL)       /usr/sbin/shutdown

taroユーザーに全てのコマンドを実行できるようにするには次の行を追加します。

taro	ALL=(ALL)	ALL

sudoコマンドの利用

sudoコマンドの書式は次のとおりです。

sudo [オプション] 実行するコマンド

コマンドを実行すると必要に応じてパスワードの入力が求められますので、「sudoコマンドを実行するユーザーのパスワード(スーパーユーザーのパスワードではない)」を入力します。

コマンド例

オプションを指定しないで実行すると、スーパーユーザー権限でコマンドを実行します。

$ sudo shutdown -h now

最初にsudoコマンドを実行する場合は、次のような注意書きか表示されます。

$ sudo shutdown -h now

We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

[sudo] password for taro: 

一度パスワードを入力すると、しばらくの間はパスワード入力なしにsudoコマンドを実行できます。

特定のユーザー権限でコマンドを実行することもできます。それには-uオプションに「ユーザー名」を指定します。

$ sudo -u ユーザー名 コマンド

-iオプションを指定すると指定されたユーザーでシェルを実行します。

$ sudo -i

上記の例では実行ユーザー名(-uオプション)を省略しているので、スーパーユーザーでシェルを実行します。これは「su -」でスーパーユーザーにスイッチするのと似ています。

-lオプションを指定すると、許可されたコマンドを確認することができます。

$ sudo -l
...
User taro may run the following commands on guest:
(ALL) /usr/sbin/shutdown

sudoのログ

sudoコマンドの実行は/var/log/secureファイルに記録されます。

$ sudo useradd hanako
[sudo] password for taro: 
$ sudo tail /var/log/secure
...
Feb  5 08:44:12 guest sudo[2197]:    taro : TTY=pts/0 ; PWD=/home/taro ; USER=root ; COMMAND=/sbin/useradd hanako
Feb  5 08:44:12 guest sudo[2197]: pam_systemd(sudo:session): Cannot create session: Already running in a session or user slice
Feb  5 08:44:12 guest sudo[2197]: pam_unix(sudo:session): session opened for user root by vagrant(uid=0)
Feb  5 08:44:13 guest useradd[2201]: new group: name=hanako, GID=1002
Feb  5 08:44:13 guest useradd[2201]: new user: name=hanako, UID=1002, GID=1002, home=/home/hanako, shell=/bin/bash
Feb  5 08:44:13 guest sudo[2197]: pam_unix(sudo:session): session closed for user root

おわりに

この記事ではsudoの基礎を解説しました。/etc/sudoersファイルの設定は簡単な例しか示していませんが、もっと柔軟にいろいろな設定ができます。本格的な設定をする場合はマニュアル等を参考にしてください。

Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
SSHポートフォワーディングの使い方(図で説明) https://monologu.com/ssh-port-forwarding/ Mon, 31 Oct 2022 08:56:32 +0000 https://monologu.com/?p=1092 Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
SSHのポートフォワーディング(SSHポート転送)機能を利用すると、ローカルホストの任意のポートに送信したデータを、リモートホストの特定のポートへ転送することができます。

POPのようなプレーンテキストのプロトコルに使用して、SSHの通信経路は使った安全な通信を行うことに利用できます。

SSHポートフォワーディングは、主に次のようなケースで利用します。

  • SSHの暗号化された通信経路を使った安全な通信を行う
  • ファイアーウォールを超えてサーバーと通信する
  • 複数ホストを経由する必要がある場合、最終ホストと直接通信しているようにアクセスする

この記事ではSSHポートフォワーディング(SSHポート転送)について、使い方や動作を図を使ってわかりやすく説明していきます。

コマンドの構文と説明に使用するケース

最初にポートフォワーディングの構文とこの記事で説明するケースを見てみましょう。

SSHポートフォワーディングの構文

SSHポートフォワーディングを行うためのsshコマンドの構文は次の通りです。

ssh -f -N -L ローカルポート番号:転送先ホスト名:転送先ポート番号 ユーザ名@SSH接続ホスト名

-fオプションはSSHの認証が完了し、リモートコマンドを実行した後にSSHをバックグランドへ移行するためのオプションです。

-Nオプションはリモートコマンドを実行しないよう指示するオプションです。上記の構文ではリモートで実行するコマンドの指定がないので、-fオプションを指定するときは-Nオプションが必要です(指定しないとエラーになる)。

-Lオプションはポートフォワーディングを指示するオプションです。「ローカルポート番号」には、ローカルマシンで使用していない任意のポート番号を指定します。

システムポート(ウェルノウンポート)の「0番〜1023番」はrootユーザーでしか使用できません。一般にシステムポートの使用は避けた方が良いでしょう。

「転送先ホスト名:転送先ポート番号」には、通信を転送する先のホスト名とそのポート番号を指定します。

残りの「ユーザ名@SSH接続ホスト名」には、SSHサーバーのホスト名とそのユーザ名を指定します。

なお、ホスト名の部分はIPアドレスでも構いません。

記事で説明するケース

この記事ではリモートのWebサーバー(80番ポート)にアクセスする次の2つのケースを例として、ポートフォワーディングの使い方とその動作を説明します。

この記事で説明するケース

ケース1:SSHサーバー経由でWebサーバーへアクセスする

ケース1はローカルマシンからWebサーバへ直接アクセスできません。しかし、SSHサーバー(ポート22番)にログインはでき、そのSSHサーバーからWebサーバー(ポート80番)にはアクセスできます。

例えば、SSHサーバーとWebサーバーが社内LANのネットワーク上にあり、外部からはSSHサーバー(踏み台サーバー)を経由しなければならない場合がこれに該当します。

このケースでポートフォワーディングを使うと、ローカルマシンがWebサーバーに直接アクセスしているかのように通信できます。

ケース2:ファイアーウォールを超えてアクセスする

ケース2はファイアーウォールでポート80番が遮断されているため、Webサーバー(ポート80番)に直接アクセスできません。しかし、SSHサーバー(ポート22番)にログインはできるケースです。

このときポートフォワーディングを使うと、ローカルマシンからサーバーで稼働するWebサーバー(ポート80番)に直接アクセスしているかのように通信ができます。

なお、ここではSSH接続にパスワード認証を使っているものと想定しています。

それではそれぞれのケースについてポートフォワーディングの使い方を見ていきましょう。

ケース1:
SSHサーバー経由でWebサーバーへアクセスする

ケース1はローカルマシンから直接Webサーバーへアクセスできないが、SSHサーバーにログインすることができ、かつSSHサーバーからはWebサーバーへアクセスできる場合です。

言い換えれば、Webサーバーへアクセスするには必ずSSHサーバーを経由する必要があるということです。

SSHサーバー経由でWebサーバーへアクセスする

上図のような構成のとき、SSHポートフォワーディングを使ってローカルマシンからWebサーバーへアクセスできるようにするには次のコマンドを実行します。

ssh -L 50080:www.example.com:80 taro@ssh.example.com

コマンドを実行するとパスワードを尋ねられますので、通常のSSH接続と同じようにパスワードを入力してSSHサーバーにログインします。

このコマンドのそれぞれの意味は次の通りです。

  • 「50080」はローカルマシンで使用するポート番号です。ローカルマシンで使用していない任意のポート番号を指定します。
  • 「www.example.com:80」の部分には、SSHの通信を転送する先のリモートホスト名とポート番号を指定します。ここではWebサーバーの80番ポートを指定しています。
  • コマンドの残りの部分は、SSHサーバーにログインするために必要なものを指定します。ここではユーザーtaroがパスワード認証でログインできることを想定しています。

これでローカルマシンの50080番ポートへの通信がWebサーバー(ポート80番)へ送られるようになります。このとき、SSH接続の経路の通信は暗号化されます。

Webブラウザでアクセスする

Webブラウザを使って実際にWebサーバーにアクセスしてみましょう。例えば、通常は以下のURLでWebページにアクセスできるとします。

http://a.example.com/page-a.html

ポートフォワーディングを使ったときはコマンドで指定したローカルマシンのポートにアクセスしますので、URLは次のようになります。

http://localhost:50080/page-a.html

URLのホスト名はローカルマシンを表すlocalhost(ループバックアドレス)、ポート番号はコマンドで指定した50080番です。

ブラウザを起動して上記のURLにアクセスするとWebサーバーからpage-aを取得してブラウザに表示されます。

ケース1の通信の詳細

このときの通信の流れを詳しく見てみましょう。

まずSSHコマンドを実行すると、ローカルマシンの任意のポート(any)とSSHサーバーの22番ポートでSSH接続が確立されます。

次にブラウザでローカルマシンの50080番ポートにアクセスすると、その通信はSSH接続の通信路を通ってSSHサーバーの22番ポートへ送られます。さらにSSHサーバの任意のポート(any)とWebサーバーの80番ポートの接続が確立され、確立された経路を通ってWebサーバーの80番ポートへ送られます。

この動作を図で表すと次のようになります。

SSHポートフォワーディングのケース1の説明図

SSH接続のパイプ(トンネル)は、ここを通る通信が暗号化されていることを表しています。

SSHコマンドをバックグラウンドで実行する

先程はWebブラウザを使いましたが、curlコマンドなどでWebサーバーにアクセスしたい場合もあるでしょう。

前述のコマンドを実行するとターミナルはSSHサーバーにログインした状態なのでローカルマシンのコマンドを実行できません。新しくターミナルを立ち上げて、そこでコマンドを実行しても良いのですが、SSHコマンドをバックグラウンドで実行すれば同じターミナルがそのまま使用できます。

それには先ほどのコマンドに-fと-Nオプションを追加します。

ssh -f -N -L 50080:www.example.com:80 taro@ssh.example.com

-fオプションは認証が完了してリモートでコマンド実行した後にSSHをバックグランドへ移行するためのオプションです。ここではリモートで実行するコマンドを指定していないので、リモートコマンドを実行しないよう指示する-Nオプションも指定します。

このようにするとSSHコマンドはバックグラウンドで実行されますので、そのままターミナルからローカルマシンのコマンドを実行できます。

例えば先ほどと同じWebページをcurlコマンドで取得するには、ターミナルで次のコマンドを実行します。URLはブラウザでアクセスする場合と同じです。

curl http://localhost:50080/page-a.html

ただしSSHコマンドはバックグラウンドで実行され続けますので、ポートフォワーディングが不要になったらプロセスをキルするといいでしょう。

それには次のようにpsコマンドでバックグラウンドで実行されているSSHコマンドのプロセスID(PID)を確認し、killコマンドでプロセスを終了します。

# ps -ef | grep ssh
...
root  3200  ...  ssh -f -N -L 50080:www.example.com:80 taro@ssh.example.com
...
# kill 3200

ケース2:
ファイアーウォールを超えて接続する

ケース2はサーバーにSSH(ポート22番)接続はできるが、そのサーバーで稼働するWebサーバー(ポート80番)には直接アクセスできないケースです。

SSHポートフォワーディングのケース2の概要図

このケースでもSSHポートフォワーディングを使うと、ローカルマシンからWebサーバーに直接アクセスしているかのように通信できます。

このときはターミナルから次のコマンドをいずれかを実行します。

ssh -L 50080:localhost:80 taro@a.example.com
ssh -L 50080:server.example.com:80 taro@a.example.com

どちらのコマンドでもやりたいことができますが、サーバー内部での動作が少し異なります。1つ目のコマンドをおすすめします。

コマンドを実行したら、通常のSSH接続と同じようにサーバーにログインします。

ケース1との違いは転送先のホスト名の指定だけです。ケース1はSSHサーバーとは異なるサーバー(Webサーバー)のホスト名を指定していましたが、今回は自分自身のホスト名(localhostあるいはserver.example.com)を指定しています。

つまり、SSH接続の経路を通ってきた通信を自身のポート80番に転送しています。

Webブラウザでアクセスする

WebブラウザでアクセスするURLもケース1と同じで、ローカルホスト自身(localhost)のポート50080番を指定します。

http://localhost:50080/page-a.html

WebブラウザでこのURLにアクセスするとpage-a.htmlを取得して表示します。

ケース2の通信の詳細

この動作を図で表すと次のようになります。

SSHポートフォワーディングのケース2の説明図

SSHコマンドを実行するとローカルマシンの任意のポート(any)とサーバーの22番ポートの間でSSH接続が確立されます。

ブラウザからローカルマシンの50080番ポートにアクセスすると、通信はSSHの通信路を通りサーバーに送られ、その通信はさらにサーバー自身のポート80番に転送されます。

ローカルマシンとサーバーAの間はSSHのトンネルを通りますので、その通信が暗号化されているのもケース1と同じです。

SSHコマンドをバックグラウンドで実行する

SSHコマンドをバックグランドで実行する方法もケース1と同様で、先ほどのコマンドに-fと-Nオプションを追加します。

ssh -f -N -L 50080:localhost:80 taro@a.example.com

curlコマンドでWebページを取得する方法もケース1と同じです。

curl http://localhost:50080/page-a.html

バックグラウンドで実行されているコマンドは不要になったらkillするようにしましょう。

localhostと実際のホスト名を指定したときの違い

先ほど次の2つのコマンドを紹介しましたが、この違いについて簡単に説明します。

ssh -L 50080:localhost:80 taro@a.example.com
ssh -L 50080:server.example.com:80 taro@a.example.com

「localhost」と「server.example.com」の指定はどちらもSSHサーバー自身を表すことになりますが、それぞれ名前解決されるとlocalhostは「127.0.0.1(ループバックアドレス)」に、server.example.comはサーバーの実際のIPアドレスに解決されます。

ループバックアドレスは論理インタフェースに紐づけられ、実際のIPアドレスは物理インタフェースに紐づけられますので、ポートフォワーディングの際、SSHサーバーのポート20番に届いた通信が論理インタフェースに紐づけられたループバックアドレスに転送されるか、物理インタフェースに紐づけらた実際のIPアドレスに転送されるかが異なることになります。

つまりSSHサーバーのポート20番に届いた通信が自身のポート80番へ転送される経路が多少異なるということです。

この経路の違いがどのように影響するかはOSの詳細に立ち入ることになり、またポートフォワーディングの使い方という本質からだいぶ外れてしまうのでこのへんにしておきますが、localhostとの通信は完全な内部通信となりますので、こちらを使った方が何かと問題が出ないと思います。

なお、私の持っている本で3冊にポートフォワードの記載がありましたが、いずれも実際のホスト名を使っていました。しかしmanコマンドで参照できるSSHコマンドのマニュアルではlocalhostを使用していました。

まとめ

SSHポートフォワーディングは、目的のサーバーにアクセスするためにSSHサーバーを経由しなければならないときに非常に便利です。

この記事ではWebサーバー(ポート80番)を例にしましたが、もちろん任意のリモートホストの任意のポートにポートフォワーディングすることが可能です。

私はファイル転送にもよくポートフォワーディングを使っていますので、最後にこの事例を簡単に説明して終わりにしたいと思います。

ポートフォワーディングを使ってファイルを転送する

このケースは次のような構成であるとします。

クライアント --- リモートホスト1 --- リモートホスト2

クライアントからリモートホスト1にSSH接続ができ、リモートホスト1からリモートホスト2にもSSH接続ができるが、クライアントからリモートホスト2には直接SSH接続できないケースです。このときクライアントでリモートホスト2からファイルを取得したいとします。

ポートフォワーディングを使わないで手順は大体次のようになるでしょう。

  1. クライアントからリモートホスト1にSSHログインする
  2. リモートホスト1からリモートホスト2のファイルをSCPで取得する
  3. クライアントからリモートホスト1のファイルをSCPやWinSCPなどのアプリケーションで取得する
  4. リモートホスト1に残っているファイルを削除する

これをポートフォワーディングを使って行うと次のようになります。

  1. クライアントでポートフォワーディング(転送先ホストはリモートホスト2のポート22番)を実行する
  2. クライアントからSCPやWinSCPなどを使って自身のポートにアクセスし、リモートホスト2のファイルを取得する
  3. ポートフォワーディングのプロセスを終了する

この説明だとあまり楽になってないように思えるかもしれませんが、実際はかなり楽できると思います。特にリモートホスト1に一時ファイルができないのが良いところです。

このほかにも便利な使い方がありますので、皆さんもSSHポートフォワーディングを是非使ってみてください。

ここから先は主に検証した内容の覚え書きです。SSHポートフォワードを使うにはここまでの記事で十分ですので、この先は読まなくても良いでしょう。興味のある方のみ先に進んでいただければと思います。

【参考】動作検証

この記事を書くにあたり仮想環境でサーバーを建てて検証を実施しました。

Webサーバへのアクセスは一瞬で終わってしまうので、この検証ではPOPサーバーにTelnetで接続して確認を行います。

ケース1の検証

仮想環境に3台のLinuxマシンを用意します。

ローカルマシン用のLinuxマシン
  • ホスト名:client
  • IPアドレス:192.168.60.10
  • 用途:SSHクライアント用
SSHサーバー用のLinuxマシン
  • ホスト名:Server1
  • IPアドレス:192.168.60.20
  • 用途:SSHサーバーが稼働。Server2のPOPサーバへもアクセス可能
POPサーバー用のLinuxマシン
  • ホスト名:Server2
  • IPアドレス:192.168.60.30
  • 用途:POPサーバ(dovecot)が稼働

clientでSSHポートフォワーディングのコマンドを実行します。Server1にSSH接続し、その通信はServer2のPOPサーバーへ転送します。

[root@client ~]# ssh -f -N -L 50110:192.168.60.30:110 taro@192.168.60.20

この状態でのclientのTCPソケットを確認します。

[root@client ~]# ss -atn
State    Recv-Q   Send-Q     Local Address:Port        Peer Address:Port    Process
...
LISTEN   0        128            127.0.0.1:50110            0.0.0.0:*
ESTAB    0        0          192.168.60.10:56474      192.168.60.20:22
...

SSHの接続が確立され、ループバックアドレス(127.0.0.1)のポート50110番がLISTENです。

clientのループバックアドレス(127.0.0.1)のポート50110番にTelnetで接続します。これでServer2のPOPサーバーに接続されました。

[root@client ~]# telnet 127.0.0.1 50110
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
+OK Dovecot ready.

このときのclientのTCPソケットを別のターミナルを立ち上げて見てみます。

[root@client ~]# ss -atn
State    Recv-Q   Send-Q     Local Address:Port        Peer Address:Port    Process
...
LISTEN   0        128            127.0.0.1:50110            0.0.0.0:*
ESTAB    0        0              127.0.0.1:50110          127.0.0.1:39152
ESTAB    0        0              127.0.0.1:39152          127.0.0.1:50110
ESTAB    0        0          192.168.60.10:56474      192.168.60.20:22
...

「127.0.0.1:50110」と「127.0.0.1:39152」との間で接続が確立されています。

Telnet接続したときの通信をclentでキャプチャした結果は次の通りです。複数のインタフェース(loとeth1)をキャプチャしたかったので、tcpdumpではなくtsharkを使いました。

[root@client ~]# tshark -i lo -i eth1 -n
...
...
1 0.000000000    127.0.0.1 → 127.0.0.1    TCP 74 39152 → 50110 [SYN] Seq=0 Win=43690 Len=0 MSS=65495 SACK_PERM=1 TSval=1165547724 TSecr=0 WS=128
2 0.000007518    127.0.0.1 → 127.0.0.1    TCP 74 50110 → 39152 [SYN, ACK] Seq=0 Ack=1 Win=43690 Len=0 MSS=65495 SACK_PERM=1 TSval=1165547724 TSecr=1165547724 WS=128
3 0.000014242    127.0.0.1 → 127.0.0.1    TCP 66 39152 → 50110 [ACK] Seq=1 Ack=1 Win=43776 Len=0 TSval=1165547724 TSecr=1165547724
4 0.010067703    127.0.0.1 → 127.0.0.1    TCP 86 50110 → 39152 [PSH, ACK] Seq=1 Ack=1 Win=43776 Len=20 TSval=1165547734 TSecr=1165547724
5 0.010095015    127.0.0.1 → 127.0.0.1    TCP 66 39152 → 50110 [ACK] Seq=1 Ack=21 Win=43776 Len=0 TSval=1165547734 TSecr=1165547734
6 0.000115681 192.168.60.10 → 192.168.60.20 SSH 166 Client: Encrypted packet (len=100)
7 0.001023164 192.168.60.20 → 192.168.60.10 SSH 118 Server: Encrypted packet (len=52)
8 0.001038095 192.168.60.10 → 192.168.60.20 TCP 66 56474 → 22 [ACK] Seq=101 Ack=53 Win=278 Len=0 TSval=1792724337 TSecr=3104266180
9 0.009896408 192.168.60.20 → 192.168.60.10 SSH 134 Server: Encrypted packet (len=68)
10 0.009914526 192.168.60.10 → 192.168.60.20 TCP 66 56474 → 22 [ACK] Seq=101 Ack=121 Win=278 Len=0 TSval=1792724346 TSecr=3104266188
^C14 packets captured

「127.0.0.1:39152」と「127.0.0.1:50110」で接続が確立され、client(192.168.60.10)とServer1(192.168.60.20)間の通信はSSH接続が使われていることがわかります。

Server1のTCPソケットの状態は次の通りです。

[root@Serve1 ~]# ss -atn
State    Recv-Q   Send-Q     Local Address:Port        Peer Address:Port    Process
...
ESTAB    0        0          192.168.60.20:37244      192.168.60.30:110
ESTAB    0        0          192.168.60.20:22         192.168.60.10:56474
...

SSH接続とServer2のPOPサーバー(192.168.60.30:110)との接続が確立されていることがわかります。

Server1でTelnet接続したときのキャプチャは次の通りです。

[root@Serve1 ~]# tshark -i lo -i eth1 -n
...
    1 0.000000000 192.168.60.10 → 192.168.60.20 SSH 166 Client: Encrypted packet (len=100)
    2 0.000146179 192.168.60.20 → 192.168.60.30 TCP 74 37244 → 110 [SYN] Seq=0 Win=29200 Len=0 MSS=1460 SACK_PERM=1 TSval=33210704 TSecr=0 WS=128
    3 0.000428286 192.168.60.30 → 192.168.60.20 TCP 74 110 → 37244 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM=1 TSval=1011476475 TSecr=33210704 WS=128
    4 0.000444294 192.168.60.20 → 192.168.60.30 TCP 66 37244 → 110 [ACK] Seq=1 Ack=1 Win=29312 Len=0 TSval=33210705 TSecr=1011476475
    5 0.000540260 192.168.60.20 → 192.168.60.10 SSH 118 Server: Encrypted packet (len=52)
    6 0.000791571 192.168.60.10 → 192.168.60.20 TCP 66 56474 → 22 [ACK] Seq=101 Ack=53 Win=278 Len=0 TSval=1792724337 TSecr=3104266180
    7 0.009096094 192.168.60.30 → 192.168.60.20 POP 86 S: +OK Dovecot ready.
    8 0.009122311 192.168.60.20 → 192.168.60.30 TCP 66 37244 → 110 [ACK] Seq=1 Ack=21 Win=29312 Len=0 TSval=33210713 TSecr=1011476484
    9 0.009349972 192.168.60.20 → 192.168.60.10 SSH 134 Server: Encrypted packet (len=68)
   10 0.009725835 192.168.60.10 → 192.168.60.20 TCP 66 56474 → 22 [ACK] Seq=101 Ack=121 Win=278 Len=0 TSval=1792724346 TSecr=3104266188
^C10 packets captured

Server1(192.168.60.20のポート37224番)とServer2(192.168.60.30のポート110番)の接続が確立され、この接続を使って通信が行われていること、およびclientとServer1の間ではSSH接続を使って通信していることがわかります。

Server2でも同じようにみてみます。TCPソケットの状態は次の通りです。

[root@Server2 ~]# ss -atn
State    Recv-Q   Send-Q     Local Address:Port        Peer Address:Port    Process
LISTEN   0        100              0.0.0.0:110              0.0.0.0:*
LISTEN   0        128              0.0.0.0:22               0.0.0.0:*
...
ESTAB    0        0          192.168.60.30:110        192.168.60.20:37244
...

Server2でキャプチャした内容は次の通りです。

[root@Server2 ~]# tshark -i lo -i eth1 -n
...
    1 0.000000000 192.168.60.20 → 192.168.60.30 TCP 74 37244 → 110 [SYN] Seq=0 Win=29200 Len=0 MSS=1460 SACK_PERM=1 TSval=33210704 TSecr=0 WS=128
    2 0.000029528 192.168.60.30 → 192.168.60.20 TCP 74 110 → 37244 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM=1 TSval=1011476475 TSecr=33210704 WS=128
    3 0.000227995 192.168.60.20 → 192.168.60.30 TCP 66 37244 → 110 [ACK] Seq=1 Ack=1 Win=29312 Len=0 TSval=33210705 TSecr=1011476475
    4 0.008548826 192.168.60.30 → 192.168.60.20 POP 86 S: +OK Dovecot ready.
    5 0.009014309 192.168.60.20 → 192.168.60.30 TCP 66 37244 → 110 [ACK] Seq=1 Ack=21 Win=29312 Len=0 TSval=33210713 TSecr=1011476484
^C5 packets captured

Server1(192.168.60.20のポート37224番)とServer2(192.168.60.30のポート110番)の接続が確立され、この接続を使ってServer1と通信していることがわかります。

ケース2の検証

ケース2では仮想環境に2台のLinuxマシンを用意します。

ローカルマシン用のLinuxマシン
  • ホスト名:client
  • IPアドレス:192.168.60.10
  • 用途:SSHクライアント用
SSHサーバー用のLinuxマシン
  • ホスト名:Server1
  • IPアドレス:192.168.60.20
  • 用途:SSHサーバーとPOPサーバが稼働するサーバー

SSHポートフォワーディングのため以下のコマンドを実行します。

# ssh -f -N -L 50110:localhost:110 taro@192.168.60.20

このときのclientのTCPソケットの状態を確認します。

[root@client ~]# ss -atn
State    Recv-Q   Send-Q     Local Address:Port        Peer Address:Port    Process
...
LISTEN   0        128            127.0.0.1:50110            0.0.0.0:*
ESTAB    0        0          192.168.60.10:56424      192.168.60.20:22
...

「127.0.0.1:50110」がLISTENで、Server1(192.168.60.20)とSSH接続が確立されています。

clientのポート50110番にTelnetで接続します。Server1のPOPサーバーと接続されました。

[root@client ~]# telnet 127.0.0.1 50110
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
+OK Dovecot ready.

POPサーバーと接続された状態のclientのソケットを確認します。

[root@client ~]# ss -atn
State    Recv-Q   Send-Q     Local Address:Port        Peer Address:Port    Process
...
LISTEN   0        128            127.0.0.1:50110            0.0.0.0:*
ESTAB    0        0              127.0.0.1:39118          127.0.0.1:50110
ESTAB    0        0          192.168.60.10:56424      192.168.60.20:22
ESTAB    0        0              127.0.0.1:50110          127.0.0.1:39118
...

「127.0.0.1:39118」と「127.0.0.1:50110」の間の接続と、SSH接続が確立されています。

Telnet接続したときの通信をclientでキャプチャした結果は次の通りです。

[root@client ~]# tshark -i lo -i eth1 -n
...
    1 0.000000000 192.168.60.10 → 192.168.60.20 SSH 166 Client: Encrypted packet (len=100)
    2 0.000614701 192.168.60.20 → 192.168.60.10 SSH 118 Server: Encrypted packet (len=52)
    3 0.000624958 192.168.60.10 → 192.168.60.20 TCP 66 56424 → 22 [ACK] Seq=101 Ack=53 Win=278 Len=0 TSval=1789555944 TSecr=3101097788
    4 -0.000129537    127.0.0.1 → 127.0.0.1    TCP 74 39118 → 50110 [SYN] Seq=0 Win=43690 Len=0 MSS=65495 SACK_PERM=1 TSval=1162379331 TSecr=0 WS=128
    5 -0.000122340    127.0.0.1 → 127.0.0.1    TCP 74 50110 → 39118 [SYN, ACK] Seq=0 Ack=1 Win=43690 Len=0 MSS=65495 SACK_PERM=1 TSval=1162379331 TSecr=1162379331 WS=128
    6 -0.000114875    127.0.0.1 → 127.0.0.1    TCP 66 39118 → 50110 [ACK] Seq=1 Ack=1 Win=43776 Len=0 TSval=1162379331 TSecr=1162379331
    7 0.009258142    127.0.0.1 → 127.0.0.1    TCP 86 50110 → 39118 [PSH, ACK] Seq=1 Ack=1 Win=43776 Len=20 TSval=1162379341 TSecr=1162379331
    8 0.009267739    127.0.0.1 → 127.0.0.1    TCP 66 39118 → 50110 [ACK] Seq=1 Ack=21 Win=43776 Len=0 TSval=1162379341 TSecr=1162379341
    9 0.009147352 192.168.60.20 → 192.168.60.10 SSH 134 Server: Encrypted packet (len=68)
   10 0.009165047 192.168.60.10 → 192.168.60.20 TCP 66 56424 → 22 [ACK] Seq=101 Ack=121 Win=278 Len=0 TSval=1789555952 TSecr=3101097796
^C10 packets captured

「127.0.0.1:39118」と「127.0.0.1:50110」で接続が確立され、client(192.168.60.10)とServer1(192.168.60.20)の間ではSSH接続でパケットがやり取りされていることが見てとれます。

このときServer1のTCPソケットは次の通りです。

[root@Serve1 ~]# ss -atn
State    Recv-Q   Send-Q     Local Address:Port        Peer Address:Port    Process
LISTEN   0        100              0.0.0.0:110              0.0.0.0:*
LISTEN   0        128              0.0.0.0:22               0.0.0.0:*
...
ESTAB    0        0              127.0.0.1:110            127.0.0.1:32800
ESTAB    0        0          192.168.60.20:22         192.168.60.10:56424
ESTAB    0        0              127.0.0.1:32800          127.0.0.1:110
...

Server1の内部(127.0.0.1)でポート32800番とポート110番ポートの接続が確立されたことがわかります。

Server1でキャプチャした結果は次の通り。

[root@Serve1 ~]# tshark -i lo -i eth1 -n
...
    1 0.000000000    127.0.0.1 → 127.0.0.1    TCP 74 32800 → 110 [SYN] Seq=0 Win=43690 Len=0 MSS=65495 SACK_PERM=1 TSval=1648130125 TSecr=0 WS=128
    2 0.000034110    127.0.0.1 → 127.0.0.1    TCP 74 110 → 32800 [SYN, ACK] Seq=0 Ack=1 Win=43690 Len=0 MSS=65495 SACK_PERM=1 TSval=1648130125 TSecr=1648130125 WS=128
    3 0.000047073    127.0.0.1 → 127.0.0.1    TCP 66 32800 → 110 [ACK] Seq=1 Ack=1 Win=43776 Len=0 TSval=1648130125 TSecr=1648130125
    4 -0.000166854 192.168.60.10 → 192.168.60.20 SSH 166 Client: Encrypted packet (len=100)
    5 0.000081507 192.168.60.20 → 192.168.60.10 SSH 118 Server: Encrypted packet (len=52)
    6 0.000445111 192.168.60.10 → 192.168.60.20 TCP 66 56424 → 22 [ACK] Seq=101 Ack=53 Win=278 Len=0 TSval=1789555944 TSecr=3101097788
    7 0.008540671    127.0.0.1 → 127.0.0.1    POP 86 S: +OK Dovecot ready.
    8 0.008632949 192.168.60.20 → 192.168.60.10 SSH 134 Server: Encrypted packet (len=68)
    9 0.008855826 192.168.60.10 → 192.168.60.20 TCP 66 56424 → 22 [ACK] Seq=101 Ack=121 Win=278 Len=0 TSval=1789555952 TSecr=3101097796
   10 0.008555632    127.0.0.1 → 127.0.0.1    TCP 66 32800 → 110 [ACK] Seq=1 Ack=21 Win=43776 Len=0 TSval=1648130133 TSecr=1648130133
^C10 packets captured

「127.0.0.1:32800」と「127.0.0.1:110」で接続が確立され、SSH接続でもパケットがやり取りされていることが見てとれます。

ループバックアドレスを指定と自身のIPアドレスを指定の違い

先程は転送先とホスト名としてlocalhostを指定しました。次のように転送先に実際のIPアドレス(192.168.60.20)を使ったときとの違いも見てみましょう。

[root@client ~]# ssh -f -N -L 50110:192.168.60.20:110 taro@192.168.60.20

結論としてはServer1内部のPOPサーバーとの接続に使用されるIPが異なります。

Telnetで接続したときのServer1のTCPソケットを見てみましょう。

[root@Serve1 ~]# ss -atn
State    Recv-Q   Send-Q     Local Address:Port        Peer Address:Port    Process
...
ESTAB    0        0          192.168.60.20:22         192.168.60.10:56470
ESTAB    0        0          192.168.60.20:110        192.168.60.20:43804
ESTAB    0        0          192.168.60.20:43804      192.168.60.20:110
...

localhost(127.0.0.1)を指定した場合、「127.0.0.1:32800」と「127.0.0.1:110」のようにループバックアドレス間で接続が確立されましたが、実IPアドレスを指定した場合は「192.168.60.20:43804」と「192.168.60.20:110のように実IPアドレス間で接続が確立されています。

このように接続に使われるIPアドレスが異なるのでServer1内部での経路が全く同じになるというわけではありません。

以上、検証はここまでとなります。ここが本当の最後になります。最後まで読んでいただきありがとうございます。

Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
コマンドでファイルの特定の行を表示する https://monologu.com/show-file-specific-line/ Sun, 13 Feb 2022 13:45:34 +0000 https://monologu.com/?p=1083 Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
ファイル全体ではなく、ファイルの一部分を数行だけ表示したいことがあります。

ここではその方法をいくつか表示します。

grepでマッチした行の前後の行も表示する

grepは指定された正規表現にマッチした行を表示します。

grep 'ssh' /etc/passwd

マッチした行の前後の行も表示したい場合は「-A」「-B」「-C」のオプションを使います。これらのオプションには、表示したい行数を続けて指定します。

「-A」はマッチした行の後の行も表示、「-B」はマッチした行の前の行も表示、「-C」はマッチした行の前後の行を表示します。

$ grep -A 2 'ssh' /etc/passwd
$ grep -B 2 'ssh' /etc/passwd
$ grep -C 2 'ssh' /etc/passwd

この例で「-A」の指定はマッチした行とその後の2行(計3行)を表示し、「-B」の指定はマッチした行とその前の2行(計3行)を表示し、「-C」はマッチした行の前2行と後2行(合計5行)を表示します。

長いオプションを知っていると上記の短いオプションも思い出しやすいでしょう。長いオプションを使うとそれぞれ次のようになります。

$ grep --after-context=2 'ssh' /etc/passwd
$ grep --before-context=2 'ssh' /etc/passwd
$ grep --context=2 'ssh' /etc/passwd

特定の行を表示する

表示する行が何行目かわかっていれば次のように表示できます。

$ sed -n '3p' /etc/passwd

sedに「-n」オプションを指定すると行を処理した結果を標準出力に出力しなくなります。このとき、行を表示したければ「p」編集コマンドを明示的に指定する必要があります。

したがってここでは3行目に対して「p」コマンドだけが指定されているので、3行目がそのまま表示されます。

次のように数値をコンマ(,)で区切って行の範囲を指定することもできます。

$ sed -n '3,5p' /etc/passwd

これは3行目から5行目までを表示します。

特定の行から末尾までを表示する

特定の行から末尾までを表示する場合、まず思いつくのはtailコマンドを使うことです。

$ tail -n 5 /etc/passwd

しかし、tailコマンドを使う場合は事前に行数を把握し、最後の何行を表示するか計算しなければなりません。

これもsedを使った方が簡単です。

$ sed -n '7,$p' /etc/passwd

「$」は最終行を表すので、これは7行目から最後の行までを表示します。

まとめ

ここで紹介した以外にも方法はいくつもあります。是非、自分の気に入った方法を見つけてみてください。

ここではファイルの特定の行を表示しましたが、grepやsedはファイルを指定しなければ標準入力から行を読み込みますので、パイプで渡された入力に対しても同様に実行できます。

「他にもこんな表示ができたらいいのに」というものがあればご連絡いただければと思います。

Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
シェルの引用符(クォーテーション)の使い方 https://monologu.com/shell-script-quotation/ Tue, 08 Feb 2022 01:47:20 +0000 https://monologu.com/?p=1074 Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
シェルには特別な意味を持つ特殊文字(メタキャラクタともいう)があります。

シェルはコマンドラインに特殊文字を見つけると、コマンドを実行する前にそれを解釈し展開などを行ってからコマンドを実行します。

例えばワイルドカード文字(*や?など)を見つけると、シェルはファイル名に展開し、$を見つけるとその後を変数名と解釈して変数の値に置き換えます。

しかし、場合によっては特殊文字を通常の文字として扱ってもらいたいときがあります。

このようなときにクォーテーションが必要になります。クォーテーションには次の3つの方法があります。

  • バックスラッシュ(\)を特殊文字の直前に置く
  • シングルクォート(')で囲む
  • ダブルクォート(")で囲む

なお、ここでクォーテーションとは特殊文字の特別な意味を奪って通常文字として扱うことを意味しています。一般には、特別な意味を奪うことを「エスケープする」と言いうことの方が多いです。

シェルの特殊文字には次のようなものがあります。

; & ( ) | ^ < > ? * [ ] $ ' " ` { } 改行 タブ スペース

つまりこれらを通常文字として使いたい場合はクォーテーションが必要になります。

バックスラッシュ(\)でエスケープする

特殊文字の直前にバックスラッシュ(\)を置くと特殊文字の特別な意味は失われ、シェルはその文字を通常文字として扱います。

このときのバックスラッシュを「エスケープキャラクタ」と呼びます。

例えば空白を含む「my file」というファイル名を次のように指定してもうまくいきません。

$ cat my file

空白はコマンドや引数を区切るための文字なので、これはcatコマンドに「my」と「file」という2つの引数を指定していることになります。

正しくは次のように記述します。

$ cat my\ file

こうすると空白の区切り文字としての意味は失われ、単なる空白文字として解釈されます。そのためcatには「my file」という1つの引数として渡されます。

バックスラッシュ(\)自身も直後の文字をエスケープするという特別な働きがありますので、「\」を通常文字と扱いたい場合はエスケープするために直前に「\」を置きます。

$ echo foo\\bar
foo\bar

なおコマンドラインで改行文字の直前に「\」を置くことによって改行文字を無視させることができますが、これは「\」をエスケープキャラクタとして使用するのとは異なります。

なぜなら改行文字の特別な意味を失わせて単なる改行文字として扱うわけではなく、「\」の直後の改行文字を無いものとして扱うからです。

シングルクォーテーション(')でエスケープする

シングルクォーテーションで囲むことによって、ほぼすべての特殊文字を通常文字として扱うことができます。

シングルクォートでエスケープできないのはシングルクォーテーションだけです。シングルクォートでシングルクォートを囲むことが不可能といった方が正確かもしれません。

例えば次のように記述してもシングルクォートでシングルクォート自身を囲めません。

$ echo 'hoo's bar'
> 

2番目のシングルクォートは1番目のシングルクォートの終わりであり、3番目のシングルクォートは新しいシングルシングルクォートの開始とみなされます。

そのためシングルクォートの後に改行すると、シングルクォートが閉じられていないのでプロンプト(>)が表示されます。

シングルクォーテーションをエスケープしたい場合は、バックスラッシュ(\)かダブルクォーテーション(")を使います。

$ echo hoo\'s bar
hoo's bar
$ echo "hoo's bar"
hoo's bar

ダブルクォーテーション(")でエスケープする

ダブルクォーテーションも、ほとんどの特殊文字をエスケープして通常文字として扱うことができます。

ダブルクォーテーションでエスケープできないのは次の3つの特殊文字だけです。

  • $
  • バッククォート(`)
  • バックスラッシュ(\)

ただし、ダブルクォート内のバックスラッシュ(\)は、の直後に「$」、「`」、「\」、「"」の文字がある場合に限ってエスケープキャラクタとして働きます。これ以外の場合、バックスラッシュは通常文字として扱われます。

反対の見方をすると、上記の文字はダブルクォートの中では必ず特殊文字として扱われるということです。

つまりこれは次のことを意味します。ダブルクォートを使う理由は主にこれらにあります。

  • 変数の参照は、変数の値に展開される
  • バッククォート(`)で囲まれたコマンドは、その結果に置換される

少し分かりづらくなってきたのでそれぞれを順に見ていきましょう。

ダブルクォーテーション(")の中での「$」や「`」の使用

ダブルクォーテーションの中で、$によるシェル変数の参照はその変数の値に展開されます。またバッククォート(`)によるコマンド置換も使えます。

次の例では両方を使っています。

$ TODAY=Today
$ echo "$TODAY is `date -I`"
Today is 2022-02-05

ダブルクォートの中の「$TODAY」はその値に展開され、「`date -I`」はそのコマンドの実行結果に置き換えられます。

ダブルクォーテーション(")の中での「\」の使用

ダブルクォートの中のバックスラッシュ(\)は、直後に「$」、「`」、「\」、「"」の文字がある場合だけエスケープキャラクタとして働きます。

先程の例で試してみましょう。

$ echo "\$TODAY is \`date -I\`"
$TODAY is `date -I`

この例で「$」や「`」はバックスラッシュでエスケープされています。そのため変数展開やコマンド置換は行われません。

もう一つ例を見てみましょう。

$ echo "\"\\\" is backslash"
"\" is backslash

ダブルクォートの中に「"」を入れたい場合、ダブルクォートの終了とみなされないように「\"」のようにエスケープする必要があります。

また最初の部分を1文字分ずつに分解すると「\"」「\\」「\"」となります。

この部分を「\"\\"」のように記述してしまうと「\"」「\\」「"」と認識されてしまいます。そのため「"」「\」「"」の文字それぞれの前に「\」を置いてエスケープする必要があります。

しかし、この場合はもちろんシングルクォートを使った方が簡単です。

$ echo '"\" is backslash'
"\" is backslash

クォートが必要なとき

上述したように特殊文字を通常文字として扱いときにエスケープが必要でしたが、この他にもクォーテーションが必要な場合があります。

例えば、次のようにtestコマンドを書くのは当然ですが正しくありません。これはtestコマンドの引数が2つだからです。

test = john

しかし次のようにヌル値との比較は正しい書き方です。

test "" = "foo"

では次のような書き方は正しいでしょうか?

test $BAR = "foo"

これは変数BARに値が入っていれば動作しますが、値がヌル値だとエラーになります。

シェル変数は値を設定しないとヌル値に初期化されます。次のような書き方はどちらもシェル変数をヌル値に初期化します。

$ BAR=
$ BAR=""

変数がヌル値だと1行目の記述は、シェルが変数を展開した後は2行目のようになります。

test $BAR = "foo"
test  = "foo"

これを避けるには変数はダブルクォートで囲む必要があります。

test "$BAR" = "foo"

この場合、変数がヌル値でも変数が展開された後は次のようになりますので正しく動作します。

test "" = "foo"

この間違いはよくありますので、覚えておきましょう。

文字列の連結を使って複数のクォーテーションを混在させる

変数展開やコマンド置換を使いたい場合はダブルクォートを使用する必要があります。それ以外の場合はシングルクォートを使うのが一般には読みやすいと思います。

ここで次のような例を考えてみましょう。

$ HOGE=hoge
$ echo "\$HOGEの値は$HOGEです。"
$HOGEの値はhogeです。

この例では変数展開が必要なのでダブルクォートを使っています。

しかし文字列は並べて書けば連結されますので、同じようなことは次の書き方でもできます。

$ echo '$HOGE'"の値は$HOGEです。"
$HOGEの値はhogeです。

シングルクォートで囲まれた部分は変数展開されませんのでそのまま「$HOGE」となります。

ダブルクォートで囲まれた部分は変数展開されるので「の値はhogeです。」になります。

したがって最終的にこれらの文字列が連結されてechoコマンドに渡されます。

この例ではそれほど便利ではありませんが、複数のクォーテーションを連結させた方がわかりやすときもありますので覚えておきましょう。

まとめ

特殊文字はコマンドを実行する前にシェルが解釈するということを忘れないようにしましょう。

コマンドがエラーになった原因や意図した通りに結果が表示されない原因が、シェルが特殊文字を置き換えてしまったということがよくあります。

特にsedやawkはコマンドの引数として特殊文字をよく使いますので、これらのコマンドへの引数をシングルクォートで囲むことがよく行われます。

Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
シェルで長いコマンドを複数行に記述する https://monologu.com/command-multi-line/ Sat, 05 Feb 2022 21:11:11 +0000 https://monologu.com/?p=1068 Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
シェルでは一般にコマンドを1行で記述し、Enterキーで改行を入力することでコマンドを実行します。

これは「改行」がコマンドラインが完結したことを意味するからです。シェルはコマンドラインの完結したことを知ると、そのコマンドラインを解釈してコマンドを実行します。

シェルスクリプトでも同様に改行によりコマンドラインが完結します。つまりシェルスクリプトでもコマンドは通常1行で記述する必要があります。

しかし、コマンドが非常に長くなってしまうと見づらくなって不便なことがあります。そのような場合のためにコマンドラインを複数行に渡って記述する方法が用意されています。ここではその方法を紹介します。

コマンドを複数行に渡って記述する

コマンドを複数行にわたって記述するには改行の直前にバックスラッシュ(\)を置きます。するとシェルはバックスラッシュの直後の「改行を無視」します。

例えば次のようにシェルに入力し、バックスラッシュ(\)の直後でEnterキーをタイプしてください。

$ echo foo \
> 

すると改行は無視され、シェルは「>」を表示して次の入力を待ちます。「>」はシェルのプロンプトで、2行目以降は通常のプロンプトとは異なる文字が使われます。この文字は環境によって異なります。

続けて次のように入力して最後にEnterキーを押します。

$ echo foo \
> bar
foo bar

ここで初めてコマンドが実行され、結果が表示されます。

このようにバックスラッシュ(\)を使うとコマンドラインは複数行に渡って書くことができます。

シェルスクリプトの中でもバックスラッシュ(\)の直後に改行することで、複数行に渡ってコマンドラインを記述することができます。

#!/bin/sh
echo foo \
     bar

ここで2行目は空白でインデントしても意味が変わらないため、見やすいようにインデントしています。

ダブルクォーテーションの中でバックスラッシュを使う

バックスラッシュを使って改行を無視させる方法は、ダブルクォーテーション(")の中でも使えます。

$ echo "foo  \
> bar"
foo  bar

ここでも改行は無視され、結果は1行で表示されます。

この場合は次のように書いても同様の効果が得られます。

$ echo "foo "\
> "bar"

改行は無視されるのでこれは「"foo ""bar"」と記述したのと同じです。並べられた文字列は連結されるので結果的に「"foo bar"」がechoの引数として渡されます。

なおダブルクォーテーション(")やシングルクォーテーション(')の中で、改行はそのまま改行文字として扱われます。

$ echo "foo  
> bar"
foo  
bar
$ echo 'foo
> bar'
foo
bar

シングルクォーテーションの中で「\」は通常文字として扱われますので、「\」の直後に改行しても、それはそのまま改行文字として扱われます。

$ echo 'foo  \
> bar'
foo  \
bar

行の分割はわかりやすいところで行う

最後に注意点として、コマンドラインはわかりやすい位置で分割しましょう。

先程の例を次のようにして書いてしまうのは間違いです。

$ echo foo\
> bar
foobar

これはfooの後に空白がないので「foobar」という1つの引数となってしまいます。

前述のように、fooの後に区切り文字である空白を記述してから、バックスラッシュと改行を使うのがわかりやすいと思います。

まとめ

バックスラッシュの直後の改行のみ改行が無視されます。

バックスラッシュと改行の間に空白などがあってはいけません。これは見た目では分かりませんので注意してください。

Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
シェルのワイルドカードの使い方 https://monologu.com/shell-wildcard/ Mon, 31 Jan 2022 02:48:06 +0000 https://monologu.com/?p=1063 Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
シェルでファイル名を指定するとき、ワイルドカードを使うことができます。

コマンドラインでワイルドカード文字を含むファイル名は、それにマッチするすべてのファイルに展開されるので、特定の文字列を含む複数のファイルを指定したりするのがとても簡単になります。

例えば次のように指定すると(*がワイルドカード文字)「.txt」で終わるファイルすべてを一覧できます。

$ ls *.txt

ワイルドカードは大変便利ですが、ファイルを操作するコマンド(特に削除コマンド)での使用には細心の注意が必要です。

中でも単独の「*」の使用は十分に注意する必要があります。意図しないファイル名が含まれないようにすることはもちろん、うっかりミスで大事故につながることもあります。これについては後述します。

ワイルドカードの種類

シェルで利用できるワイルドカードの種類には次のようなものがあります。

ワイルドカード意味
*0個以上の任意の文字列
?1文字
[...][ ]の中の任意の1文字
[!...][ ]に中の任意の文字以外の1文字

コマンドラインにこれらのワイルドカード文字があれば、シェルはマッチするファイル名に展開してからコマンドを実行します。

どのように展開されるかは「set -x」でトレース情報の表示を有効化した後、echoコマンドを実行して確認できます。

例えばa.txt、b.txt、c.txtというファイルのあるディレクトリで次のようにコマンドを実行してみてください。

$ set -x
+ set -x
...
$ echo *.txt
+ echo a.txt b.txt c.txt
a.txt b.txt c.txt
...

「*」は0個以上の任意の文字列を表すので「*.txt」はa.txt、b.txt、c.txtのいずれにもマッチします。したがって「*.txt」は「a.txt」「b.txt」「c.txt」に展開されてechoコマンドに渡されます。

+ echo a.txt b.txt c.txt

この後はそれぞれのワイルドカードの指定方法の例をいくつか紹介します。

*を使う例

「*」は0個以上の任意の文字列を表します。以下にいくつか指定方法の例を挙げます。

*foo           # fooで終わるファイル。fooというファイルがあればそれも含む
foo*	       # fooで始まるファイル。fooというファイルがあればそれも含む
*foo*	       # fooを含むファイル。fooで始まるものやfooで終わるものも含む
a*c            # aで始まりcで終わるファイル
 *             # カレントディレクトリのすべてのファイル(ただし隠しファイルは除く)
bar/*	       # barというディレクトリ下の全ファイル(ただし隠しファイルは除く)

?を使う例

「?」は任意の1文字を表します。以下にいくつか指定方法の例を挙げます。

??	       # 名前が2文字のファイル
foo??          # fooの後に2文字が続くファイル
??*	       # 名前が2文字以上のファイル

[...]や[!...]を使う例

[...]は[と]で囲まれた文字列のうちの1文字を表します。[の直後に!を続けると否定の意味になります。つまり[と]で囲まれた文字列のうちの文字以外の1文字を表します。

[ab][xyz]      # 1文字がa、bのいずれか、2文字目がx、y、zのいずれかのファイル
[ab][xyz]??    # 最初の2文字は上の例と同じだがその後に任意の2文字が続く

[...]や[!...]では「-(ハイフン)」を使って範囲を指定することもできます。

[a-z]*	       # アルファベットの小文字で始まるファイル
[-a-z]*	       # -(ハイフン)か、アルファベットの小文字で始まるファイル
[a-zA-Z]       # アルファベットの小文字か大文字で始まるファイル
*[0-9][0-9]    # 2桁の数字で終わるファイル
[!0-9]*	       # 数字では始まらないファイル

ただし国際化対応などでUnicodeなどのASCII以外の文字セットが使われるようになると文字の範囲は単純ではなくなってきています。

範囲指定を使う場合は、上記のようにアルファベットの小文字、大文字、数字の範囲に限った方が良いでしょう。

隠しファイル

「.」で始まるファイル名は「隠しファイル」と呼ばれます。これは単にlsコマンドを実行しただけでは通常は表示されないためそのように呼ばれます。

ワイルドカードを使うときも隠しファイルは例外です。隠しファイルにマッチさせたい場合は、最初の文字が.であると明示的に指定する必要があります。

そのため前述したワイルドカードの指定例はすべて隠しファイルにはマッチしません。

以下にいくつか例を示します。

.*	       # 隠しファイルすべて。ただし.や..も含まれる
.bash*	       # .bashで始まるファイル
bar/.*	       # barディレクトリの隠しファイルすべて

ただし、次のように指定しても隠しファイルは含まれませんので注意してください。

[.b]*	       # bで始まるファイル。.で始まるファイルは含まれない

また次のように指定して、隠しファイルと通常のファイルすべてを指定するのも間違いです。

.?*            # 隠しファイル全てと同じ。通常のファイルは含まれない

ファイルを操作するコマンドでは十分に注意しよう

ワイルドカードは大変便利ですがファイルを操作するコマンド、特に削除コマンドを実行するときには十分注意しましょう。

例えば最も恐ろしいのは次のような間違いです。

rm -rf *
rm -rf /*

カレントディレクトリの中のファイルやディレクトリをすべて削除するつもりが、うっかり2番目のようにタイプしてしまったときです。これを実行したことはありませんが、これはうっかりでは済まされない大惨事です。

ここまではないにしても次のようなミスもあるでしょう。

rm tmp/*
rm /tmp/*

カレントディレクトリの下のtmpディレクトリの中のファイルを消そうとして、2番目のようにタイプしまう間違いです。2番目のコマンドを実行するとシステムの/tmpディレクトリのファイルを消してしまいます。

このように相対パスと絶対パスを間違ってで実行してしまうと全く異なるディレクトリを操作してしまうため被害は甚大です。

このようなミスを犯さないための一つの方法として、echoやlsのような安全なコマンドで事前に確認します。

echo *
echo /*

このようにすれば2番目の指定が間違っていることに気づくでしょう。

また単独の*などはなるべく使わずに、もっと細かな指定も検討しましょう。

まとめ

ワイルドカードはファイルグロブ(あるいは単にグロブ)とも呼ばれます。プログラミング言語ではファイルグロブという用語が使われていることがありますので、この用語についても覚えておきましょう。

繰り返しになりますがうっかりミスには十分に気をつけましょう。コマンドを実行する前に一呼吸置いて確認することや、前述のように安全なコマンドで確認する癖をつけましょう。

Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
logrotateの設定の仕組みを解説する https://monologu.com/explain-logrotate-setting-mechanism/ Sun, 09 Jan 2022 03:26:27 +0000 https://monologu.com/?p=1027 Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
アプリケーションにとってログファイルはイベントやエラーなどを記録しておく重要なファイルです。しかし、同じファイルにログを書き込み続けていくとログファイルはどんどん肥大化していくことになります。

ログが肥大化すると次のような不都合があります。

  • ディスク領域を圧迫する
  • ログを確認するとき、ファイルを読み込むのも特定の場所を探すのも大変

そのためログファイルが肥大化しないように適切なログのローテーションが必要です。

Linuxではシステムにある多くのログファイルをローテートするために設計されたlogrotateプログラム(コマンド)が用意されています。

この記事ではLinuxでlogrotateがどのように設定され使われているのか、その仕組みを紐解いていきます。

なおlogrotateについては「logrotateのテスト方法」という記事も書きましたので参照いただけると幸いです。

logrotateによるログローテートの仕組み

ログのローテートとは一般に元のログファイルを別の名前で保存し、新しいログファイルに切り替えることを言います。

実際にlogrotateプログラムを使ってログローテートを体験して、ローテーションの仕組みをみていきましょう。

この章は少し長いですが、ここに書いてあることがわかればlogrotateについて7割は理解できたも同然ですので、しばらくお付き合いください。

独自のログローテーションを設定する

まずテスト用にローテートされるログファイルを作成します。

# echo "original log file" > /var/log/mytest.log
# ls -l /var/log/mytest.log
-rw-r--r--. 1 root root 18 Jan  6 14:33 /var/log/mytest.log

ここでは「original log file」という内容の/var/log/mytest.logというファイルを用意しました。

次にこのファイルをローテートするための設定ファイルを作成します。

# cp -p /etc/logrotate.d/syslog /etc/logrotate.d/mytest
# vi /etc/logrotate.d/mytest
# cat /etc/logrotate.d/mytest
/var/log/mytest.log {
    create
    rotate 2
}

/etc/logrotate.d ディレクトリの中のファイルをコピーして、mytestというファイルを作成します。ここではsyslogファイルをコピーしています。

mytestを作成したら、catで表示している内容にviで編集してください。

この設定の「/var/log/mytest.log」部分はローテート対象のファイルを指定します。その後の中括弧の本体に、そのファイルをどのようにローテートするかの命令(ディレクティブという)を記述します。

デフォルトではローテーションが実行されるとオリジナルのログファイルは別の名前にリネームされます。すると元のファイル(ここではmytest.log)がなくなってしまうので、createディレクティブで元ファイルと同じ名前のファイルを新しく作成するよう指示しています。

「rotate 2」というディレクティブは、リネームされた古いファイルは2つまで残しておくという指示です。それ以上の古いファイルは削除されます。

独自のログローテーションを実行する

さてこれで準備は整いました。では実際に/var/log/mytest.logをローテートさせてみましょう。

それには次のコマンドを実行します。

logrotate -f /etc/logrotate.d/mytest 

「-f」は強制的にローテートさせるためのオプションです。logrotateコマンドには先程作成した/etc/logrotate.d/mytestを引数に渡します。

ローテーションがどのように行われたか確認してみましょう。

# ls -l /var/log/mytest.log*
-rw-r--r--. 1 root root 0 Jan  6 14:30 /var/log/mytest.log
-rw-r--r--. 1 root root 5 Jan  6 14:29 /var/log/mytest.log.1

さっきはなかった「mytest.log.1」というファイルが確認できます。中身を見てみましょう。

# cat /var/log/mytest.log.1
original log file

このことから「mytest.log.1」は元々「mytest.log」だったことがわかります。デフォルトでは、この例のように元のファイル名の後に番号を追加した名前で保存されます。

もう一度同じように実行してみましょう。

# logrotate -f /etc/logrotate.d/mytest
# ls -l /var/log/mytest.log*
-rw-r--r--. 1 root root  0 Jan  6 14:39 /var/log/mytest.log
-rw-r--r--. 1 root root  0 Jan  6 14:34 /var/log/mytest.log.1
-rw-r--r--. 1 root root 18 Jan  6 14:33 /var/log/mytest.log.2
# cat /var/log/mytest.log.2
original log file

一番最初のオリジナルファイルはmytest.log.2になりました。

さらにもう一度実行すると「original log file」と記載されたファイルは削除されて無くなります。

# logrotate -f /etc/logrotate.d/mytest
# ls -l /var/log/mytest.log*
-rw-r--r--. 1 root root 0 Jan  6 14:43 /var/log/mytest.log
-rw-r--r--. 1 root root 0 Jan  6 14:39 /var/log/mytest.log.1
-rw-r--r--. 1 root root 0 Jan  6 14:34 /var/log/mytest.log.2

これは「rotate 2」という指示で、古いファイルは2世代までしか保存しないからです。それより古いファイルは削除されます。

このようにローテートするとファイルは適切なサイズで切り替えられ、古いファイルは自動的に削除されるのでディスク領域を圧迫することもありません。

最後にここでのローテートの動作をまとめます。ローテートされると次のようなことが行われています。

  1. 「mytest.log.2」を「mytest.log.3」にリネーム
  2. 「mytest.log.1」を「mytest.log.2」にリネーム
  3. 「mytest.log」を「mytest.log.1」にリネーム
  4. 「mytest.log」を新たに作成
  5. 「mytest.log.3」を削除

このようにファイルをローテートするとファイルはリネームされて切り替えられ、古いファイルは削除されるのが基本的な動作です。

ここでmytest.log.3 というファイルが一旦作成されるのは安全性を考慮してでしょう。必要な世代のファイルが作成されてから削除することにより、エラーなどでファイルが失われることがありません。

システムのログローテーションはcronで実行されている

先程はlogrotateコマンドを手動で実行してログローテートを行いましたが、Linuxなどのシステムでは毎日ログローテーションが行われています。

これはcronでlogrotateコマンドが毎日実行されるようにスケジュールされているからです。

/etc/cron.dailyディレクトリを覗いてみましょう。するとlogrotateというスクリプトファイルがあるはずです。

# ls -l /etc/cron.daily/
total 4
-rwxr-xr-x. 1 root root 189 Jan  4  2018 logrotate

/etc/cron.dailyディレクトにあるファイルはcronデーモンによって毎日実行されますので、 logrotateスクリプトが毎日実行されています。

logrotateスクリプト中身を見てみましょう。

# cat /etc/cron.daily/logrotate
#!/bin/sh

/usr/sbin/logrotate /etc/logrotate.conf
...

このようにlogrotateスクリプト実行されると「/etc/logrotate.conf」を引数としてlogrotateコマンド(ここではフルパスで指定されいる)が実行されることがわかります。

システムのlogrotate.confファイルの中身

独自のログローテートは「/etc/logrotate.d/mytest」というファイルを指定してlogrotateコマンドを実行しましたが、システムのログローテーションでは「/etc/logrotate.conf」が使われていることがわかりました。

このlogrotate.confの中身を見てみましょう。ここでは設定の意味も簡単にコメントしています。

$ cat /etc/logrotate.conf
# see "man logrotate" for details
# rotate log files weekly
weekly      ← 毎週ローテートする

# keep 4 weeks worth of backlogs
rotate 4    ← ローテートした古いファイルは4世代まで保持する

# create new (empty) log files after rotating old ones
create      ← ローテートした後に新しいファイルを作成する

# use date as a suffix of the rotated file
dateext     ← 古いファイルの名前に日付を追加する

# uncomment this if you want your log files compressed
#compress   ← ローテートしたファイルを圧縮する

# RPM packages drop log rotation information into this directory
include /etc/logrotate.d  ← logrotate.dの中のファイルを読み込めという指定

# system-specific logs may be also be configured here.

最後の「include /etc/logrotate.d」の記述は、/etc/logrotate.dディレクトリの中のファイルを読み込むという指示です。

個々のログファイルの設定をlogrotate.confに記述することもできますが、「/etc/logrotate.d」の下にサービスごとに個別のファイルを用意するのが一般的です。

このようにしておくと、ローテートの設定ファイルを「/etc/logrotate.d」に放り込むだけでログローテートの設定が完了できるので便利です。

したがって先程作成した/etc/logrotate.d/mytestもlogrotate.confから読み込まれ毎日実行されることになります。

そのほかlogrotare.confに記述されている裸のディレクティブはグローバルな設定です。これよりもmytestのようにファイル名の後の中括弧本体で指定したディレクティブの方が優先されます。

/etc/logrotate.dの中のサービスごとの設定ファイル

ここまでの説明でログローテートについて基本的なことはすべて説明しました。指定できるディレクティブ以外にあと知っておくことは、どのような設定ファイルの記載方法があるかです。

サンプルとして/etc/logrotate.dの中のサービスごとの設定ファイルをいくつか簡単にみてみましょう。

一つ目はシステムログ(syslog)の設定です。

# cat /etc/logrotate.d/syslog
/var/log/cron
/var/log/maillog
/var/log/messages
/var/log/secure
/var/log/spooler
{
    missingok
    sharedscripts
    postrotate
	/bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
    endscript
}

最初に複数のファイルパスがリストされています。このように複数のファイルに対して一度に同じディレクティブを設定することもできます。

ここでrotateやcreate(と衝突する)のディレクティブが指定されていないのでlogrotare.confのでrotateやcreateディレクティブにしたがってローテートされます。

次のように複数のファイルを指定するためにワイルドカードを使うこともできます。

# cat chrony 
/var/log/chrony/*.log {
    missingok
    nocreate
    ...

次はdnfの設定をみてみましょう。

# cat /etc/logrotate.d/dnf
/var/log/dnf.librepo.log {
    missingok
    notifempty
    rotate 4
    weekly
    create 0600 root root
}

/var/log/hawkey.log {
    missingok
    notifempty
    rotate 4
    weekly
    create 0600 root root
}

ここでは2つのファイルに対して別々にディレクティブが設定されています。

よく使うディレクティブ

最後によく使うディレクティブを簡単に説明します。

ティレクティブ説明
compress ローテーションした古いログファイルを圧縮する。デフォルトではgzipで圧縮する。
copytruncate 元のログファイルをリネームするのではなく、コピーを作成した後に元のログファイルを空にする。
この場合、元のログファイルは存在したままなのでcreateの指定は無効になる。
create mode owner group, create owner group ローテーション直後に新しいログファイルを同じ名前で作成する。
モード、所有者、グループ所有者を指定できる。これらが省略された場合は元のログファイルと同じ値になる。
daily 毎日ローテートする。
dateext 単純な番号を付ける代わりに、古いログファイルにYYYYMMDDのような日付を付ける。
delaycompress 1つ前のログファイルを圧縮するのを、次のローテーションまで遅らせる。
これはcompressが指定された場合のみ機能する。
ifempty ログファイルが空でもローテートする。これはデフォルトの動作です。
include file_or_directory fileを指定した場合はインラインで指定されたのと同様に読み取り、ディレクトリが指定された場合は、そのディレクトリの中のファイルを読み込む。
mail address ログがローテーションによって存在期間を超えた場合、指定したメールアドレスに送信される。
maxage count count日よりも古いログファイルを削除する。
ログがローテートされるときのみこれがチェックされる。
maxsize size 指定のサイズよりログのサイズが大きくなった場合、追加で指定されている時間間隔(daily, weekly, monthly, yearly)に関わらずローテートする。
minsize size 指定のサイズよりログのサイズが大きくなった場合にローテートする。ただし、追加で指定されている時間間隔(daily, weekly, monthly, yearly)が経過するよりも前にはローテートしない。
missingok ログファイルがない場合でもエラーを報告しない。
monthly 毎月ローテートする(通常は月の初日)。
nocompress ローテートされた古いログファイルを圧縮しない。
nodateext 古いログファイルに日付を付加しない。
nomissingok ログファイルが存在しないとエラーを報告する。これはデフォルトの動作です。
notifempty ログファイルが空の場合、ローテーションしない。
olddir directory ログをローテートすると古いファイルは指定したディレクトリに移動される。
postrotate/endscript postrotateとendscriptの間のスクリプトが、ログがローテートされた後に実行される。
prerotate/endscript ログがローテートされる場合に限り、prerotateとendscriptの間のスクリプトがローテート前に実行される。
rotate count ログファイルはcount回数だけローテートする。
つまりcount世代分のログファイルは保持され、それより古いファイルは削除される。countに0にすると古いファイルは1つも保持されない。
size size 指定されたサイズよりもログファイルのサイズが大きい場合のみローテーションします。
サイズの後ろに”k”をつけるとキロバイト、”M”をつけるとメガバイト、”G”をつけるとギガバイトと解釈される。
sharedscripts prerotateとpostrotateスクリプトはローテートされるログファイルごとに実行され、そのログファイルへの絶対パスがスクリプトの最初の引数として渡される。
つまり、前述のsyslogやワイルドカード指定のように複数のログファイルに対してスクリプトが毎回実行されるということです。このディレクティブが指定された場合、このようなときでもスクリプトは一度だけしか実行されません。ただし、ファイルが一つもローテートされなければスクリプトは実行されない。
weekly 毎週ローテートする。

まとめ

logrotateによるログローテートの仕組みは意外とシンプルだったでしょう。

ここで説明した以外のディレクティブについて知りたいときはmanコマンドで確認できます。

Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
logrotateのテスト方法 https://monologu.com/how-to-test-logrotate/ Sun, 09 Jan 2022 02:39:52 +0000 https://monologu.com/?p=1032 Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
logrotateの設定ファイルを修正したり、あるいは新たに追加した場合にはログのローテーションをテストする必要があるでしょう。

ここではそのログローテーションのテストの方法を説明します。

Linuxシステムのログローテーション

まずはLinuxなどのシステムが、どのようにログのローテーションを行なっているか確認することから始めます。

システムでは「/etc/cron.daily/logrotate」でログのローテーションを行なっています。

#!/bin/sh

/usr/sbin/logrotate /etc/logrotate.conf
EXITVALUE=$?
if [ $EXITVALUE != 0 ]; then
    /usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]"
fi
exit $EXITVALUE

このようにcronによって毎日次のコマンドが実行されていることがわかります。

/usr/sbin/logrotate /etc/logrotate.conf

システムのログのローテーションの基本的なことについては「logrotateの設定の仕組みを解説する」の記事を参照してみてください。

ログローテーションのテスト方法

システムのログローテーションの実行方法がわかったところで、これをどのようにテストするか、その方法を説明していきます。

logrotateコマンドをデバッグモードで実行する

logrotateコマンドにはデバッグモードがあります。デバッグモードで実行した場合は何も変更されないので安心して実行できます。

システムが実行しているのと同じことをデバッグモードで実行してみましょう。デバッグモードで実行するには「-d(--debug)」オプションを使います。

# logrotate -d /etc/logrotate.conf

デバッグモードではどのようにログローテートを行なっているか、その詳細なメッセージが表示されます。

エラーになっているものがないか、このメッセージから確認できます。

-vオプションで詳細を表示する

実際にログをローテートさせるときは、「-v(--verbose)」オプションを付けると、デバッグモードと同じように何が行われているか詳細を表示してくれます。

ただしローテーションが行われると、古いファイルは削除されてしまうので注意してください。

# logrotate -v /etc/logrotate.conf

-fオプションで強制的にログをローテートする

先ほどの「-v」だけを指定しただけでは、ローテートする必要がないログファイルはローテーションが行われません。

「-f(--force)」オプションを使うとローテーションが必要ない場合でも、強制的にローテーションをさせることができます。

# logrotate -fv /etc/logrotate.conf

ここでは何が行われているかわかるように-vオプションも指定しています。

しかし、強制的にローテーションを行なっても、例えば日付をサフィックスに持つ古いファイルがあると、同じようなファイルが作成できないなどのエラーが出力される場合があります。そのような点を意識してテストします。

logrotate.statusファイルを確認する

logrotateコマンドを実行した際のメッセージを見ればわかるとおり、コマンドを実行すると「/var/lib/logrotate/logrotate.status」ファイルからローテーションの状態を読み込みます。

ローテートを実行したあとは、このファイルでローテーションの状態が確認できます。

# cat /var/lib/logrotate/logrotate.status
logrotate state -- version 2
"/var/log/sssd/sssd_implicit_files.log" 2022-1-6-2:0:0
"/var/log/mytest.log" 2022-1-7-19:55:19
"/var/log/dnf.librepo.log" 2022-1-7-19:55:19
...

また、logrotateコマンドはこのファイルを参照して前回のローテート状態を確認してローテーションの必要性を判断しています。そのためこのファイルを編集して日付を偽る(過去に戻す)ことによってローテートさせることもできます。

個別の設定ファイルで確認する

ここまでシステムのログローテーションと同じように「logrotate.conf」を引数としてlogrotateコマンドを実行してきました。

しかし、/etc/logrotate.dディレクトリの一部のファイルを修正したり、このディレクトリにファイルを追加した場合などに毎回全ての設定(logrotate.conf)を読み込むのは非効率かもしれません。

このような場合、修正したファイルだけを指定して実行します。

# logrotate /etc/logrotate.d/syslog

ここではsyslogファイルだけを引数に渡して実行しています。

ただしこの方法は少し注意が必要です。

logrotate.confを読み込んでいないため、そこに設定されているディレクティブも読み込まれていません。そのため、logrotate.confを引数として実行した場合と動作が異なることがあります。

したがって最終的にはlogrotate.confを読み込んでテストした方が良いでしょう。

まとめ

きちんとログがローテートされないとログが肥大化して最終的にはシステムのディスクの領域を埋め尽くしてしまうかもしれません。

ログローテーションのテスト自体は楽しいものではありませんが、重要なものですのできちんとテストを実施しましょう。

またログのローテーション基本が知りたい場合には「logrotateの設定の仕組みを解説する」の記事を参照ください。

Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
Outlookのテンプレートをマクロで動的に修正する https://monologu.com/modify-outlook-template-by-macro/ Tue, 28 Dec 2021 00:03:43 +0000 https://monologu.com/?p=1014 Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>
Outlookのテンプレートは使って定型文(や宛先、件名など)を保存しておけば、同じ内容のメールを作成するのがとても簡単で便利ですよね。

この記事では、さらにマクロを使ってOutlook テンプレートを動的に修正してもっと便利にする方法を紹介します。

この記事を読めば次のことがわかります。

  • Outlookテンプレートの作成方法
  • マクロからテンプレートを呼び出す方法
  • テンプレートの内容をマクロで動的に修正する方法

メールのテンプレートを作成する

最初にOutlookのテンプレートの作成手順を説明します。

  1. 「ホーム」メニューの「新しい 電子メール」をクリックします。
  2. 宛先、件名、メッセージ本文などテンプレートとして使いたい内容を入力します。
  3. 「ファイル」メニューの「名前を付けて保存」をクリックします。
  4. 名前を付けて保存のダイアログボックスで、「ファイルの種類」で「Outlook テンプレート(*.oft)」を選択します。
  5. 保存場所を選択し、「ファイル名」に任意の名前(拡張子.oft)を入力して「保存」ボタンをクリックします

これでOutlookのテンプレートの作成は完了です。

テンプレートを使うときは保存した.oftファイルをダブルクリックで開きます。するとOutlookで保存されたメッセージが開きますので、適宜修正して使うことができます。

テンプレートをOutlookのマクロから呼び出す

テンプレートをダブルクリックして開くのと同じことをマクロで実行してみましょう。

Outlookに開発タブを表示する

Outlookに開発タブが表示されていない場合は、次の手順で表示させておきましょう。

  1. 「ファイル」タブ、「オプション」をクリックします。
  2. Outlookのオプションで「リボンのユーザー設定」をクリックし、「開発」のチェックボックスにチェクを入れます。

テンプレートをマクロで表示する

次にマクロからテンプレートを開いて表示させてみましょう。

  1. 「開発」タブ > 「Visual Basic」をクリックするか、「Alt+F11」で「Microsoft Visual Basic for Applications」を起動します。
  2. 「Project1」を右クリックし、「挿入」 > 「標準モジュール」を選択して標準モジュールを追加します。

    既に「Module1」などの標準モジュールがあればそれを利用しても構いません。

  3. ここでは「Module1」にマクロを追記します。「Module1」が開かれていなければ「Module1」をダブルクリックして開きます。

  4. 次のコードを記述します。

    Application.CreateItemFromTemplateには先ほど保存したテンプレートのパスを指定します。

    Sub displayTemplate()
      Dim myItem As Outlook.MailItem
      Set myItem = Application.CreateItemFromTemplate("C:\foo\sample.oft")
      myItem.Display
    End Sub

    このコードの詳細は後述します。

  5. コードを記述し終えたら「Ctrl+S」で上書き保存します。
  6. 「開発」タブ > 「マクロ」の右側の「▼」をクリックして「Project1.displayTemplate」をクリックしてマクロを実行します。

これで先ほどと同じようにテンプレートが表示されれば成功です。

「マクロ」を「クイック アクセス ツール バー」に追加しておくと、もっと簡単にマクロを実行できるようになります。

それには「開発」タブ > 「マクロ」で右クリックして、表示されたメニューの「クイック アクセス ツール バーに追加」をクリックします。

テンプレートを表示するマクロの詳細

先ほどのコードの中身を見てみましょう。

Dim myItem As Outlook.MailItem
Set myItem = Application.CreateItemFromTemplate("C:\foo\sample.oft")

Application.CreateItemFromTemplateメソッドの引数にはOutlookテンプレート(.oft)ファイルへのパスを指定します。

このメソッドはメールのメッセージを表すOutlook.MailItemオブジェクトを返します。

myItem.Display

myItem.Displayは別ウィンドウにメッセージを表示します。

このようにしてテンプレートをダブルクリックしたときと同じような動作になります。

動的にテンプレートのメッセージを修正するマクロ

先ほどはテンプレートのメッセージを表示するだけでした。

ここではマクロを使ってテンプレートの内容を動的に修正してみます。動的に修正することによって、例えばマクロを実行した日付の挿入するなどもっと便利にすることができます。

ここではメッセージの次の2つを動的に修正します。

  • 件名
  • 本文

この例ではテンプレートの「件名」と「本文」に次の文字列を含む「作業報告.oft」を使います。

MM/DD 作業報告

まずは全体のコードです。

Sub workReport()
  Dim myItem As Outlook.MailItem
  Set myItem = Application.CreateItemFromTemplate("C:\foo\作業報告.oft")

  today = FormatDateTime(Now, vbShortDate)
  mmdd = Right(today, 5)

  myItem.subject = Replace(myItem.subject, "MM/DD", mmdd)
  myItem.body = Replace(myItem.body, "MM/DD", mmdd)
  myItem.body = myItem.body & vbCrLf & "今日の作業はXXXです。"

  myItem.Display
End Sub

このマクロを実行すると件名と本文の「MM/DD」を今日の日付に置き換えます。さらに本文には、改行した文字と"今日の作業はXXXです。"という文字列が追加されます。

それでは新しく追加されたコードを順に見ていきましょう。

次のコードは日付を取得します。

today = FormatDateTime(Now, vbShortDate)
mmdd = Right(today, 5)

FormatDateTime(Now, vbShortDate) は、今日の日付を短い形式(例:2022/01/21)で返します。

Right(today, 5) で月と日の文字列を切り出しています。これによりmddには「01/21」が格納されます。

次のコードは件名と本文の文字列MM/DDを先ほどの月日で置き換えます。

myItem.subject = Replace(myItem.subject, "MM/DD", mmdd)
myItem.body = Replace(myItem.body, "MM/DD", mmdd)

ここでは MailItemのSubjectとBodyプロパティを使っています。それぞれのプロパティの意味は次の通りです。

MailItem.subject
メッセージの「件名」の文字列を設定または返します
MailItem.body
メッセージの「本文」の文字列を設定または返します

したがって先ほどの2行は「件名」と「本文」の「MM/DD」という文字列ををmmdd(01/21)の値に置き換えたあと、MailItem.SubjectおよびMailItem.Bodyへ再び設定しています。

最後にメッセージ本文に改行文字と「今日の作業はXXXです。」という文字列を追加しています。

myItem.body = myItem.body & vbCrLf & "今日の作業はXXXです。"

まとめ

Outlookのテンプレートは便利ですが、マクロで動的に修正することによってさらに柔軟性が得られて便利になります。

ここでは日付の挿入と文字列の追加を行いましたが、マクロを使えばどんなことでもできます。こうできたらいいな、ということがあったら挑戦してみてはいかがでしょうか。

Copyright © 2024 アナグマのモノローグ All Rights Reserved.

]]>