見出し画像

Linux標準教科書を深める

はじめに

本記事は、私が社内で行ったLinux勉強会で解説した内容をまとめたものになります。
目次を見ての通り、本記事にLinuxの基礎的な内容を全部詰め込んでいて、テーマに沿った記事分け等を一切していないですし、記事としての体裁はイマイチなので調べ物をするのには向いていない記事ですが、読み物として読んでいただければと思います。

対象者はLinux初心者や、Linuxにあまり慣れていない人です。
数年使っている人には常識レベルのことが多いかもですが、「そうだったんだー」と思える豆知識や小技もちょこちょこ紹介しています。

社内で勉強会をした時は、LPI-Japanが公開しているLinux標準教科書を読みながら、自分が長年使っていて「実際はこういう使い方をする方が多いかな」とか「こういうコマンドやオプションも便利だよ」みたいなものを紹介するような形となっていました。
ですので、まずはLinux標準教科書を一通り読んでみたり、教科書とこちらの記事を合わせて読み進めたりするといいかもです。
以下からダウンロードできます。
https://linuc.org/textbooks/linux/  

記事で紹介しているコマンド等の順番は、教科書に登場する順番と大体同じになっています。また、教科書に記載してあって特に補足が不要だと思った部分については本記事では特に紹介していないので、本記事だけでは基本を学ぶのには十分ではないことはご了承ください。
※ちなみに教科書の後半の内容についてあまり記載がないのですが、これは私がまとめる時に力尽きたせいです。

コンソール上の操作全般の便利技・豆知識

最初に、Linux操作をするにあたって覚えておくと便利な技をご紹介します。
ちょっとしたことですが、覚えておくとLinux上の作業がはかどります。

以前打ったコマンドの実行


目的のコマンドが出てくるまで「↑」を連打。
「history」コマンドも使えます。

タブ補完

パスやコマンドを途中まで打った後にTabキーを押すと候補が表示される技。
この性質を利用して、ファイルやディレクトリが存在するかを確認してから実行したい場合は 敢えて全部自分で打たずに、最後の1文字を残してタブ補完すると安全。
※読み取り権限がないファイルや実行権限がないコマンドは補完されないので注意(sudoで実行しているとタブ補完ができないことがあります)。

カーソルを先頭に戻す

「Ctrl+a」
コマンドを最後まで打ち込んだ後にコマンドの先頭までカーソルを戻したい場合は、「←」キーを 連打するのではなく、「Ctrl+a」を打つと一瞬で戻せます(「home」でもいける)。
※間違えて隣の「s」を押すとコンソールにロックがかかるので、「Ctrl+z」で解除。

カーソルを末尾に戻す

「↑」キー +「↓」キー
一旦前回のコマンドを表示した後に、現在打ち込み中のコマンドラインに戻すと、自然とカーソルが
末尾に戻る裏技(「end」キーでもいける)。

コマンドラインのキャンセル

「Ctrl+c」
コマンドラインを途中まで打って最初からやり直したい場合は、打った文字列をバックスペースで 消すのではなくて、「Ctrl+c」でキャンセルすると楽。

コンソール上の文字列のコピー

単語(スペースや区切り文字っぽいもので区切られた文字列)であれば、単語上でダブルクリック するだけで選択可能(先頭から末尾までのドラッグは不要なので時短&確実)。
TeraTerm等では選択=コピー状態になるので、貼り付けは右クリックするだけ(コンソールにもよって挙動は異なる)。

システムの言語を英語にする(環境変数を変更)

$ export LANG=C

デフォルトは「LANG=ja_JP.UTF-8」。

コマンドの使い方を確認する

$ <コマンド> --help
もしくは
$ man <コマンド名>

manコマンドは「q」でページ切り替えができる(最後のページまで行くと終了して元の画面に戻る)。
-a オプションを付けると「C から呼び出すときの使い方」などの詳細情報も閲覧できる。
左上に表示される数字はセクション番号。「man 1 <コマンド>」等で該当セクションのみの表示も可能。

<セクション番号一覧>
1. コマンド
2. システムコール
3. ライブラリ関数(C言語)
4. スペシャルファイル(/dev/)
5. 設定ファイル
6. ゲーム
7. 雑多な事(慣習、プロトコル、文字コードなど)
8. システム管理

ファイル操作系コマンド

コマンドのバイナリ本体の置き場所を調べる

$ which ls
alias ls='ls --color=auto'
/bin/ls

whereisコマンド等もありますが、whichコマンドはエイリアスまで確認できるので便利。

cdコマンドで1つ前にいた場所に戻る

$ cd -

あくまでも1つ前の場所に戻るだけなので、連続で叩いても同じ場所を行ったり来たりするだけです(行ったり来たりしたい時は便利)。

プロセスツリーを表示

$ ps -e f

※psは「aux」派が多いかと思いますが、個人的にはツリー表示できる上記がおすすめです。

mvコマンドが実質リネームである話

mvコマンドはファイルを移動するコマンドですが、実質的にはファイルのリネームです。移動元のファイルのファイル名情報のみを移動先のファイル名(パス)に変更しています。
ですので、ディスク上のデータも移動前と同じ、アクセス権や所有者、タイムスタンプ等のファイルの付加情報についても移動前と同じとなります。
一旦コピーして元のファイルを消しているわけではなく、ファイル名の書き換えのみなのでmvコマンドはファイルサイズに寄らず、すぐに完了します。
※cpコマンドはデータもコピーするので、ファイルサイズが大きいと時間がかかります。

renameコマンドで一括リネーム

以下のように指定することにより、ファイル名を一括置換することができます。

$ rename <変換前文字列> <変換後文字列> <対象ファイル>

例えば以下のように「aaa_」から始まる大量のファイルがあり、何らかの事情により「aaa_」が「bbb_」に変更になったためファイル名を全部置換したい!!という場合、以下のようなコマンドラインを実行することで一発で置換できます。

$ ls -1
aaa_20210201220000.txt
aaa_20210201230000.txt
aaa_20210202000000.txt
aaa_20210202010000.txt

$ rename aaa bbb aaa_* ★「aaa_」を含むすべてのファイルの「aaa」を「bbb」に変換

$ ls -1 ★確認
bbb_20210201220000.txt
bbb_20210201230000.txt
bbb_20210202000000.txt
bbb_20210202010000.txt

ディレクトリ操作系コマンド

mkdirでディレクトリを作る

$ mkdir <ディレクトリパス>

再帰的に作成したいときは「-p」オプションを使う。

rmdirでディレクトリを消す

$ rmdir <空のディレクトリ>

ディレクトリが空じゃないと削除できないので注意。
中身ごと消したい時は「rm -r」を使う。

ファイル参照系コマンド

catコマンド

$ cat <ファイルパス>

標準出力にダラーっと出力されるので、大きなファイルを見たい時は不向きです。

ちなみに、複数ファイルを指定すると、全ファイルの内容が繋がって出力されます。それを利用して、ファイルの結合に使ったりします(分割されたバイナリファイルの結合等)。
※というか、それが本来の使い方です(catはconcatenate(連結)の略)。

$ cat <ファイル1> <ファイル2> <ファイル3> > hoge.txt

(参考)
splitコマンドを使ったファイルの分割

$ split -b <1ファイルのサイズ>  -d <ファイルパス> <分割ファイルプレフィクス>

(例)test-01、test-02…というファイル名で分割

$ split -b 1MB  -d test.png test-

headコマンドとtailコマンド

ファイルの先頭だけ見たい時はhead、お尻だけ見たい時はtailを使う。
どちらもデフォルトでは10行のみ表示される。

$ head <ファイル名>
$ tail <ファイル名>

-{行数}オプションを指定すれば、指定した行数分見れる(以下は30行の例)。

$ head -30 xxx.log

頭から30行目だけ見たいとかいう場合は以下のように組み合わせることで実現可能。
※ただ、もう少しいいやり方があったはず…。

$ head -30 <ファイル名> | tail -1

上で示す通り、headやtailはファイルだけでなく標準出力にも使用可能なので、以下の結果は同じ。

$ head <ファイル名>
$ cat <ファイル名> | head

tail -fについて

ログを監視する時におなじみのコマンド。
最終10行を表示した後、ファイルの更新を監視して最終行に追加されていった内容を順次表示する。
監視をやめたい時にはCtrl+cを実行。

$ tail -f <ファイル名>

grepと併用すると、より監視がしやすかったりする(以下はERRORを引っかける例)。

$ tail -f <ファイル名> | grep "ERROR"

headとtailに複数ファイル指定

headとtailは複数ファイル同時に指定可能。
ファイル名も見出しっぽく表示してくれるので、エビデンスを残す時に敢えて複数並べてheadをたたくと見やすいし楽なのでオススメ。

以下はすべてのインデックスファイルの頭3行を表示する例。

$ head -3 *.csv
==> aaa.csv <==
111,222,333,444
111,222,333,444
111,222,333,444
==> bbb.csv <==
111,222,333,444
111,222,333,444
111,222,333,444
==> ccc.csv <==
111,222,333,444
111,222,333,444
111,222,333,444

ちなみにtail -fにも複数指定可能です。
複数モジュールのログを同時に監視したい時とかにオススメ。

moreコマンド

スペースでページ送りをするのが特徴。

lessコマンド

矢印キーでスクロールできるのが特徴。
操作はviに近い。

viewコマンド(オススメ)

こちらも操作はviに近い。

<よく使う操作>
・文字列の検索

/検索ワード

「n」で次のマッチへ、「Shift + n」で前のマッチへ

・ファイルの最後にジャンプ
「shift + G」

・ファイルの先頭にジャンプ
「1」→「shift + G」
※元々は最初に押した行番号に飛ぶので、「10」→「shift + G」だと10行目にジャンプ。

・行番号表示

:set number

・viewの終了

:q

・指定した行に飛ぶ

:<行番号>

・ファイルを開いたタイミングで指定した行に飛ぶ

$ view <ファイル名> +<行番号>

ファイル一覧取得系コマンド

findコマンド

指定したディレクトリ配下のファイルを再帰的に一覧表示します。

カレントディレクトリのファイルだけを探したい場合は「-maxdepth 1」をつけて実行(1つ下の階層までであれば2指定して実行)。

$ find -maxdepth 1 -type f

ちなみに「-maxdepth 1」と「-type f」を逆に書くと怒られます。

また、findコマンドはlsコマンドと違ってファイル名でソートをかけてくれるとは限らないので(この辺はファイルシステムによる)、一覧をファイル名順に並べたい時は、パイプで繋いでsortコマンドを使います(sortコマンドについては後述)。

標準出力と標準エラー出力

Lniux のコマンドは一つの入り口(標準入力)と二つの出口(標準出力、標準エラー出力)がある

標準入力   :コマンドを打つ入力そのもの
標準出力   :コマンドの実行結果
標準エラー出力:コマンド叩いてエラーが起きた時の結果

標準出力と標準エラー出力の切り分けについて

Linux上のコマンドの標準出力と標準エラー出力は↑に記載した通りですが、厳密に言うとプログラム内でコンソールにメッセージを出力する時に、出力先を標準出力としているか標準エラー出力としているかでどちらに出力されるかが決まります。
C言語の場合は、fprintfという関数を使うと明確に切り分けることができます(printfだと必ず標準出力)。
fprint関数の第一引数に「stdout」を指定すると標準出力、「stderr」を指定すると標準エラー出力に出力されます。

・標準出力
fprintf(stdout, "stdout: aaaaaaaaa\n");

・標準エラー出力
fprintf(stderr, "stderr: aaaaaaaaa\n");

どんな言語でも大体の場合はどちらに出力するかは明示的に指定できるようになっているので、プログラムを書く側になった場合は意識してみるといいかもです。

リダイレクト

リダイレクトとは、標準出力の出力先をコンソール上ではなくファイル等に変更することです。
出力を完全に捨てたい場合(どこにも出力しない場合)は出力先として「/dev/null」を指定します。

リダイレクトの種類

「>」  :上書き
「>>」  :ファイルに追記

リダレクト記号の前に付与する文字

「1」:標準出力のみが対象となる
     ※「>」と「1>」は同じこと。通常「1」は省略する
「2」:標準エラー出力のみが対象となる
「&」:標準出力と標準エラー出力の両方が対象となる

(例1)標準出力を上書きリダイレクト
 ※ 標準エラー出力はコンソール上に表示される

$ コマンドライン > ファイル

(例2)標準エラー出力を上書きリダイレクト
 ※標準出力はコンソール上に表示される

$ コマンドライン 2> ファイル

標準出力と標準エラー出力の切り替え

「1>&2」:標準出力を標準エラー出力に切り替える。
「2>&1」:標準エラー出力を標準出力に切り替える。

リダイレクトを複数書いた場合、コマンドラインの後ろから解釈されることに注意(詳しくは以下)。

リダイレクト使用例

・標準出力と標準エラー出力を両方捨てる(出力を抑える)。
 ※標準エラー出力を標準出力に切り替えてから、標準出力をファイルにリダイレクト。

$ コマンドライン > /dev/null 2>&1
もしくは
$ コマンドライン &> /dev/null

・標準出力と標準エラー出力を両方ファイルに書き出す。
 ※標準エラー出力を標準出力に切り替えてから、標準出力をファイルにリダイレクト。

$ コマンドライン > ファイル 2>&1
もしくは
$ コマンドライン >> ファイル 2>&1

・標準出力と標準エラー出力を別々のファイルに出力
 ※それぞれの出力先に別のファイルを指定。

$ コマンドライン > ファイル1 2> ファイル2

・標準出力のみを追記書き込みして標準エラー出力は捨てる。
 ※標準エラー出力を捨ててから追記。

$ コマンドライン >> ファイル 2> /dev/null

・標準エラー出力のみを追記書き込みして標準出力は捨てる。
 ※標準出力を捨ててから標準エラー出力を標準出力に切り替えて追記。

$ コマンドライン >> ファイル 2>&1 > /dev/null

・標準出力はファイルに、標準エラー出力は標準出力としてコンソールに出力。
 ※標準出力をファイルに出力してから、標準エラー出力を標準出力に切り替える。

$ コマンドライン 2>&1 > ファイル

シェルスクリプト内でLinuxコマンドを実行した時の標準出力

以下のようなtest.shを用意して実験してみます。

#!/bin/bash
output=$(cat $1)
echo "output: $output"

$()を使うとコマンドの標準出力を左辺に格納できますが、「標準出力を」というところがポイント。
標準エラー出力が出力された場合等にどうなってしまうのかを見ていきます。

・存在するファイルを指定した場合(標準出力が出力される場合)

ファイルの中身を確認
$ cat test.txt
1234567890

実行
$ ./test.sh test.txt
output: 1234567890

↑「output:」の見出しとともに中身が表示される

・存在しないファイルを指定した場合(標準エラー出力が出力される場合)

$ ./test.sh aaaaaa.txt
cat: aaaaaa.txt: そのようなファイルやディレクトリはありません ★標準エラー出力が漏れ出てくる
output:

↑エラーメッセージに「output:」の見出しはつかず、「output:」の見出しの後は空。要するに、エラーメッセージは$outputに格納されておらず、コンソール上にたれ流れている状態。

・標準エラー出力も受け取りたい場合
 ※標準エラー出力を標準出力に切り替える(catコマンドの最後に「2>&1」を付ける)

#!/bin/bash
output=$(cat $1 2>&1)
print "output: $output\n"

この状態で実行。

$ ./test.sh aaaaaa.txt
output: cat: aaaaaa.txt: そのようなファイルやディレクトリはありません

↑「output:」の見出しとともにエラーメッセージが表示される。
つまり、先ほどとは違い、エラーメッセージが$outputに格納されている。

・標準出力はファイルに出力し、標準エラー出力は変数に入れる場合
 ※標準出力を捨てた後、標準エラー出力を標準出力に切り替える。

#!/bin/bash
output=$(cat $1 2>&1 > stdout.txt);
print "output: $output\n"

この状態で実行。

<ファイルがある時>
$ ./test.sh test.txt
output:

↑text.txtの内容がstdout.txtに出力されるため、コンソールには出力されない。

<ファイルがない時>
$ ./test.sh aaaaaa.txt
output: cat: aaaaaa.txt: そのようなファイルやディレクトリはありません

↑「output:」の見出しとともにエラーメッセージが表示される。stdout.txtは空。

パイプについて

コマンドの標準出力を、パイプで繋いだ次のコマンドに標準入力として渡すことができます。
渡すのはあくまでも標準出力のため、標準エラー出力を渡す場合「2>&1」で切り替える必要があります。

例)lsの結果をパイプで渡してlessで見る

$ ls -l /usr/bin | less

パイプの使用例

・複数条件のAND条件で絞り込む
 zipでgrepした結果を更に2008で絞り込む

$ ls -l /usr/bin | grep zip | grep 2008
-rwxr-xr-x. 1 root root       2953 1011 02:40 2008 zipgrep

・絞り込んだ行から必要な部分だけを表示
 zipでgrepした結果を更に2008で絞り込み、更に半角スペース区切りの4つ目だけ表示

$ ls -l /usr/bin | grep zip | grep 2008 | cut -d ' ' -f 4
root

文字列検索(grep)

grepでよく使うオプション

・-vオプション
「zip」にマッチした行から「2010」を含む行を除外

$ ls -l /usr/bin | grep zip | grep -v 2010

・-eオプション
「zip」か「tail」にマッチする行をgrep(複数条件のOR条件)

$ ls -l /usr/bin | grep -e zip -e tail

・正規表現等を使った検索
「zip」だけを検索(指定の仕方をちょっと工夫する)

$ ls -l /usr/bin | grep " zip$"
-rwxr-xr-x  1 root root     216008 1112 00:26 2010 zip

・-wオプション
「zip」を単語検索

$ ls -l /usr/bin | grep -w "zip"
-rwxr-xr-x. 1 root root       3303 1111 21:49 2010 gpg-zip
-rwxr-xr-x  1 root root     216008 1112 00:26 2010 zip

・-aオプション
検索文字列にマッチした行の前後2行も含めて表示

$ ls -l /usr/bin | grep -a2 " zip$"
-rwxr-xr-x. 1 root root       2022 1112 07:25 2010 zforce
-rwxr-xr-x. 1 root root       4975 1112 07:25 2010 zgrep
-rwxr-xr-x  1 root root     216008 1112 00:26 2010 zip
-rwxr-xr-x  1 root root     110376 1112 00:26 2010 zipcloak
-rwxr-xr-x. 1 root root       2953 1011 02:40 2008 zipgrep

・-Aオプション
検索文字列にマッチした行の後ろ2行も含めて表示

$ ls -l /usr/bin | grep -A2 " zip$"
-rwxr-xr-x  1 root root     216008 1112 00:26 2010 zip
-rwxr-xr-x  1 root root     110376 1112 00:26 2010 zipcloak
-rwxr-xr-x. 1 root root       2953 1011 02:40 2008 zipgrep

・-lオプション
ファイルの中に検索文字列を含む場合、ファイル名だけを表示

$ grep -l ERROR *
xxx.log
yyy.log

・検索対象ファイル指定

$ grep ERROR xxx.log

以下のようにcatで開いてからパイプで繋いでgrepすることもできる(結果は同じ)

$ cat xxx.log | grep ERROR

ファイルを指定しないといつまでも入力を待ち続けるので注意

$ grep ERROR ★このまま止まる(Ctrl+cでキャンセル可能)

・-iオプション
大文字と小文字の区別をしない

$ grep -i error xxx.log
ERROR: xxxxxxx
xxxxx error xxxxxxxx
(以下略)

・-cオプション
マッチした行数を表示

$ grep -c ERROR xxx.log
3

・-nオプション
行番号も表示

$ grep -n alert AlertArakawa10min.pl
111: ERROR:xxxxxx
152: ERROR:xxxxxxxxx

・-rオプション
カレント配下のサブディレクトリまで再帰的に検索

$ grep -r ERROR *

正規表現として使われる記号をgrepする方法色々

サンプルファイルtest.txtの内容

aaaaa
bbbb
****
^
$
□ ★空行

上記ファイルから「****」だけをgrepしたい場合はどうするか。

1.素直に「****」を指定してみる。

$ grep **** test.txt
test.pl:my $err = `cat test.txt aaaaa 2>&1 > output.txt`;

不思議な現象が発生。たぶん足元のファイル一覧が*で展開された上で何かが引っ掛かった。
※どういう原理かは不明。

2.「****」を""でくくってみる。

$ grep "" test.txt
aaaaa
bbbb
****
^
$
□

全部引っかかった。失敗。

3.頭に「^」、お尻に「$」をつけてみる

$ grep ^****$ test.txt
****
□

「****」と空行が引っ掛かった。

4.↑を""で囲ってみる

$ grep "^$" test.txt
****
□

変わらない。意味合い的にはたぶん3と同じになっている。

5.更にエスケープもしてみる

$ grep ^**$ test.txt
****
□

変わらない。たぶんエスケープが効いておらず、3,4と同じ扱いになっている。

6.↑から「^」と「$」を取ってみる

$ grep \*\*\*\* test.txt
aaaaa
bbbb
****
^
$
□

全部引っかかる。

3~6の結果を踏まえると、「*」を複数個並べたり、正規表現の後にくっつけて書くと、最初の1つは文字としての「*」、2つ目以降は正規表現として扱われるっぽい。

なので、3~5は全部「*から始まる0回以上の繰り返し」と判断されたので、「*」を含む行と「^$」だけの行(空行)が引っ掛かったと思われる。
※最後の$はただの飾りで何の役にも立ってない。

2と6は「^」がないので、「から始まる」が抜けて「*の0回以上の繰り返し」と判断されて全部引っかかったのでは。

7.[*].*としてみる

$ grep [*].* test.txt
****

見事「***」だけをgrepすることに成功。
[]で囲まれた文字列は正規表現として展開されないので、「*と任意の1文字の0回以上の繰り返し」として解釈された。

ちなみに正規表現として展開しないようにするには、シングルクォートを使うのが一般的なので、通常はこんな感じ↓

$ grep '*'.* test.txt
****

また、前述した通り、「*と任意の1文字の0回以上の繰り返し」として解釈されているので、test.txtの中身が以下のような場合は、「」だけの行も引っかかる。

aaaaa
bbbb
****
^
*
$
□
$ grep '*'.* test3.txt
****
*

上記コマンドラインから最後の「」を取っ払うことにより、「*と任意の1文字」と解釈されて、「****」だけが引っ掛かる。

$ grep '*'. test.txt
****

8.より確実に「****」だけを引っかける

上記の書き方だと「*****」とか「*aaa」でも引っかかってしまう。
より確実に書くならこんな感じ。

・ダブルクォートでくくってエスケープして「^」と「$」をつける

$ grep ^"****"$ test3.txt
****

エスケープする時はダブルクォートかシングルクォートでくくらないとエスケープが有効にならないので注意。

・[*]を4つ並べて「^」と「$」をつける

$ grep ^[*][*][*][*]$ test3.txt
****

ちなみにこの書き方をシングルクォートでやるとダメっぽい。

$ grep ^'*''*''*''*'$ test.txt
****
*****
*
□

あとシングルクォートが正規表現として展開しないのは1文字限定っぽいので、これもダメ。

$ grep ^'****'$ test.txt
****
*****
*
□

変数の展開について

Linuxの世界では、以下に明確な違いがあります。
・変数をシングルクォートでくくる
・変数をダブルクォートでくくる
・変数をダブルクォートやシングルクォートでくくらない

<シングルクォートでくくった場合>
 ・変数が展開されず、ただの文字列として扱われる。

<ダブルクォートでくくった場合>
 ・変数の中身が展開される。
 ・変数の中身の改行を保持する。

<ダブルクォートやシングルクォートでくくらない場合>
 ・変数の中身が展開される。
 ・変数の中身の改行を保持しない(半角スペースに変わる)。

<変数が展開されるかどうかの確認>

$ aaa="AAA"; echo '$aaa'
$aaa ★展開される
$ aaa="AAA"; echo "$aaa"
AAA ★展開されない
$ aaa="AAA"; echo $aaa
AAA ★展開されない

<改行が含まれるかどうかの確認>

ダブルクォートでくくる

$ line=`cat test.txt`; echo "$line"
111
222
333

ダブルクォートでくくらない

$ line=`cat test.txt`; echo $line
111 222 333

変数に格納した内容をそのまま表示したい場合は、基本的にダブルクォートでくくります。
ただし敢えて1行で表現したい場合は、ダブルクォートでくくらないという裏技も使えます。

ファイル・文字列処理系コマンド

touchコマンド

存在しないファイルを指定すれば空ファイルを作成する。
存在するファイルを指定すればタイムスタンプが現在時刻になる。

$ touch <ファイル名>

時刻を指定したい場合は-tを指定。

$ touch -t 202101010900.00 <ファイル名>

更に-aを指定するとAccess Timeだけが、-mを指定するとModify Timeだけが変わる。

$ touch -a -t 202101010900.00 <ファイル名>

タイムスタンプの確認方法

statコマンドで細かいタイムスタンプを確認可能です。

$ stat output.txt
File: `output.txt'
Size: 19              Blocks: 8          IO Block: 4096   通常ファイル
Device: fd00h/64768d    Inode: 1142652     Links: 1
Access: (0777/-rwxrwxrwx)  Uid: (  502/testuser)   Gid: (  502/testuser)
Access: 2021-08-29 12:54:54.970886066 +0900
Modify: 2021-08-29 12:54:31.862515496 +0900
Change: 2021-08-29 12:54:31.862515496 +0900

Access Time:
 ファイルにアクセスした時刻。Windowsの「アクセス日時」もコレ。
 ファイルの中身を見たりすると変わる。更新時は変わらない。
Modify Time:
 ファイルを更新した時刻。Windowsの「更新日時」もコレ。
 ファイルの中身を更新したりすると変わる。属性変更時は変わらない。
Change Time:
 ファイルを変更した時刻。Windowsの「作成日時」はなぜかコレ。
 更新時や属性変更時(ファイル名・アクセス権・タイムスタンプ等の変更時)に変わる。

作成時刻(Create Time)は持っていない。
※以下のオプションで作成時刻を取れると見かけたが、「?」になってしまった。

$ stat -c "%W" output.txt
?


diffコマンド

ファイルの差分を取る時に使います 。
Windowsの場合であればWinMerge等のソフトで事足りるのですが、Linux環境上でしか触れないファイルとかの差分を 取りたい場合には、diffコマンドを使って差分を取ります。

・ファイルの差分を取りたい場合

$ diff <ファイル1> <ファイル2>

・ディレクトリ丸ごと差分を取りたい場合

$ diff -r <ディレクトリ1> <ディレクトリ2>

オプションによって差分の見え方が異なるので、一番見やすいオプションを使ってみてください。
以下のような2ファイルを用意し、各オプションの実行結果を貼り付けます。

diff1.txt     diff2.txt
---------------  ---------------
AAAA        AAAA
BBBB        DDDD
CCCC        CCCC
---------------  ---------------

・オプションなしの場合

$ diff diff1.txt diff2.txt
2c2
< BBBB
---
> DDDD

・-uオプションの場合(多分一番わかりやすい)

$ diff -u diff1.txt diff2.txt
--- diff1.txt   2021-09-03 12:53:55.539533085 +0900
+++ diff2.txt   2021-09-03 12:54:27.690516147 +0900
@@ -1,3 +1,3 @@
AAAA
-BBBB
+DDDD
CCCC

・-cオプションの場合

$ diff -c diff1.txt diff2.txt
*** diff1.txt   2021-09-03 12:53:55.539533085 +0900
--- diff2.txt   2021-09-03 12:54:27.690516147 +0900
***************
*** 1,3 ****
AAAA
! BBBB
  CCCC
--- 1,3 ----
AAAA
! DDDD
CCCC

cmpコマンド

ファイルの差分を取るコマンドには、diff以外にcmpコマンドというものがあります。こちらは最初の差分が何バイト目に出てくるかを教えてくれるだけのシンプルなものです。バイナリデータとかの差分を確認したい場合に便利です。

・テキストファイルの差分を取った場合(diffの時と同じファイルを指定)

$ cmp diff1.txt diff2.txt
 diff1.txt diff2.txt 異なります: バイト 6、行 2

・バイナリデータの差分を取った場合

$ cmp aaa.bin bbb.bin
aaa.bin bbb.bin 異なります: バイト 147、行 3

・バイナリデータの差分をdiffで取った場合(差分があることしかわからない)

$ diff aaa.bin bbb.bin
バイナリー・ファイルaaa.binとbbb.binは違います

差分を取りたいものや状況によって使い分けるといいと思います。

sortコマンド

ファイルやコマンドの出力結果をソートしたい場合は以下のように実行します。

$ sort <ファイル名>
 もしくは
$ <コマンドライン> | sort

・ -rオプション
 逆順でソート

・-nオプション
 数値としてソート
 (例)du の結果をサイズの昇順にソートしたい場合
 ・sortを指定せずに実行
  →サイズ順にはならない

$ du /bin/* | head
0       /bin/Mail
44      /bin/[
108     /bin/a2p
12      /bin/abrt-action-analyze-backtrace
12      /bin/abrt-action-analyze-c
4       /bin/abrt-action-analyze-ccpp-local
8       /bin/abrt-action-analyze-core
12      /bin/abrt-action-analyze-oops
12      /bin/abrt-action-analyze-python
4       /bin/abrt-action-analyze-vmcore

 ・sortのみを指定して実行
  →辞書式ソートになるのでサイズ順にならない

$ du /bin/* | head | sort
0       /bin/Mail
108     /bin/a2p
12      /bin/abrt-action-analyze-backtrace
12      /bin/abrt-action-analyze-c
12      /bin/abrt-action-analyze-oops
12      /bin/abrt-action-analyze-python
4       /bin/abrt-action-analyze-ccpp-local
4       /bin/abrt-action-analyze-vmcore
44      /bin/[
8       /bin/abrt-action-analyze-core

sortに-nオプションを指定して実行 → サイズ順に並ぶ

$ du /bin/* | head | sort -n
0       /bin/Mail
4       /bin/abrt-action-analyze-ccpp-local
4       /bin/abrt-action-analyze-vmcore
8       /bin/abrt-action-analyze-core
12      /bin/abrt-action-analyze-backtrace
12      /bin/abrt-action-analyze-c
12      /bin/abrt-action-analyze-oops
12      /bin/abrt-action-analyze-python
44      /bin/[
108     /bin/a2p

・-kオプション
 半角スペース目の何列目でソートをかけるかを指定可能
 (例)llの結果をサイズの昇順にソートする場合
   (半角区切りの5番目でソート)

$ ll /var/log | head | sort -k 5 -n
-rw-------. 1 root   root        0 117 03:24 boot.log
合計 572
drwx------. 2 root   root       23  73 19:26 audit
drwxr-xr-x. 2 root   root      204  73 19:26 anaconda
-rw-------  1 root   root     8183  711 10:43 boot.log-20210711
-rw-------  1 root   root     8225  811 09:09 boot.log-20210811
-rw-------  1 root   root     8238  816 09:20 boot.log-20210816
-rw-------  1 root   root     8326  917 10:17 boot.log-20210917
-rw-------  1 root   root    16496  716 18:17 boot.log-20210716
-rw-------  1 root   root    32968 1019 15:35 boot.log-20211019

・-tオプション
  -kオプション指定時に、区切り文字を指定可能
 (例)llの結果を更新時間の分の部分でソートする場合
   (':'区切りの2番目でソート)

$ ll /var/log | head | sort -t ':' -k 2
-rw-------  1 root   root     8225  811 09:09 boot.log-20210811
-rw-------  1 root   root     8326  917 10:17 boot.log-20210917
-rw-------  1 root   root    16496  716 18:17 boot.log-20210716
-rw-------  1 root   root     8238  816 09:20 boot.log-20210816
-rw-------. 1 root   root        0 117 03:24 boot.log
drwx------. 2 root   root       23  73 19:26 audit
drwxr-xr-x. 2 root   root      204  73 19:26 anaconda
-rw-------  1 root   root    32968 1019 15:35 boot.log-20211019
-rw-------  1 root   root     8183  711 10:43 boot.log-20210711

<sortコマンドを使う場面>

  • 複数モジュールのログを時系列で見る
    →複数ログを指定してsortすると出力時間でソートがかかるので、時系列で追いかける時便利。

  • findの結果をファイル名昇順に並べ替える
    →lsの結果は基本的にファイル名昇順になってますが、findの結果はぐちゃぐちゃなので基本的にsortを併用した方が見やすいし使いやすい。

  • 異なるファイル名の後ろにタイムスタンプがついていて、ファイル名によらずタイムスタンプ順にソートする
    →エクセルでもソートしにくいパターン。
     全てのファイルについて時系列に並べたい時に便利。

基本的には出力結果を見やすいように&使いやすいように並べ替える時に使う感じです。

uniqコマンド

ファイルやコマンドの出力結果の重複行を1行にまとめたい時はuniqを使いますが、同じ行が複数行続いている部分しかまとめてくれないので、基本的には以下のようにsortをかませて使用します。

$ sort <ファイル名> | uniq
 もしくは
$ <コマンド出力結果> | sort | uniq

(例)以下のようなファイルを用意する

test5.txt
-----------
AAA
BBB
AAA
CCC
CCC
DDD

・sortせずにuniq

$ uniq test5.txt
AAA
BBB
AAA
CCC ★CCCが2行連続していたところしか1行にまとまっていない(AAAはそのまま)
DDD

・sortしてuniq

$ sort test5.txt | uniq
AAA ★AAAもちゃんと1行にまとまった
BBB
CCC
DDD

重複した行数の取得

uniqコマンドに-cオプションを指定すると、重複行が何行あったか表示することができます。

$ sort test5.txt | uniq -c
2 AAA
1 BBB
2 CCC
1 DDD

grepと組み合わせて、重複している行を調べる時に便利です。

$ sort test5.txt | uniq -c | grep -v " 1 "
2 CCC
2 EEE

trコマンド

文字を置き換える時に使用する。  
 「文字列」ではなく「文字」なので、以下のような感じになる。

$ cat test.txt | tr abc def
          →aをdに変換
           bをeに変換
           cをfに変換

<変換前>

$ cat tr.txt
aaa
bbb
ccc

<変換後>

$ cat tr.txt | tr abc def
ddd
eee
fff

<trの使いどころ>

シェルスクリプトの中で大文字小文字の区別をせずに文字列判定をしたい場合等に、文字列を全部小文字に揃えるとか、全部大文字に揃えるみたいな用途でしか見たことがありません。
 
・全部大文字にする

$ cat tr.txt | tr [a-z] [A-Z]

・全部小文字にする

$ cat tr.txt | tr [A-Z] [a-z]

ちなみに文字ではくて文字列の置換には「sed」を使います(こちらについては別途説明)。


cutコマンドの話

文字列を切り取ってくれるコマンドです。

・バイト指定(-bオプション)
 -bの後に取り出したいバイト数を指定して、「〇文字目~〇文字目まで」みたいに切り出します。

(例)

$ echo "12345678" | cut -b1-3
123
$ echo "12345678" | cut -b4-5
45

・フィールド指定(-dと-fオプション)
 -dに区切り文字、-fにフィールド番号を指定して、「〇区切りの〇個目」みたいに切り出します。

(例)

ハイフン区切りの2番目
 $ echo "123-456-78" | cut -d '-' -f2
 456

カンマ区切りの2番目
 $ echo "123,456,78" | cut -d ',' -f2
 456

カンマ区切りの12番目
 $ echo "123,456,78" | cut -d ',' -f1-2
 123,456

cutコマンドを使いこなすと、例えばコマンドの実行結果を部分的に絞り込みたい時とかに便利です。あとツールを作る時にも便利です。

<1つ以上のスペースで区切られた文字列を切り出したい場合>

「cut -d ' '」 だと、本当に半角スペース1つだけでしか区切ってくれないので、2つ以上スペースが続いているところも区切りとみなしたい場合には上手く切り出せません。

$ cat test.txt
 1 2 3  4 5 6 7 ★4の前を半角スペース2つにする
 1 2 3 4 5 6 7
$ cat test.txt  | cut -d ' ' -f4
  ★1行目が上手く取れていない
 4

こういう場合はawkを使うと上手く切り出せます。

$ cat test.txt  | awk '{print $4}'
 4
 4

awkはプログラム言語みたいなもので色々できるのですが、使いこなすのが難しいし処理が遅いのであんまり使うことはありません。
でも要所要所で使うと結構便利です。詳しくは以下のあたりが参考になります。
 http://www.atmarkit.co.jp/ait/articles/1706/02/news017.html

文字列の置換(sedコマンド)

文字列を置換にはsedコマンドを使います。
大体以下のような感じで使います。

$ cat test.txt | sed -e "s/{変換前文字列}/{変換後文字列}/g"

(例)普通に置換

$ echo "aabbcc" | sed -e 's/aa/dd/g'
ddbbcc

(例)正規表現を使って置換①

$ echo "aabbcc" | sed -e "s/[a-b]/d/g"
ddddcc

(例)正規表現を使って置換②

$ cat test5.txt | sed -e "s/^A.*/EEE/g"
 EEE
 BBB
 EEE
 CCC

sedコマンドでファイルの中身を置換

-iを使ってファイルを指定すると、ファイルを直接編集できます。

$ cat test5.txt
 AAA
 BBB
 AAA
 CCC

$ sed -i -e "s/^A.*/EEE/g" test5.txt

$ cat test5.txt
 EEE
 BBB
 EEE
 CCC

sedコマンドの区切り文字の話

sedコマンドの置換前文字列と置換後文字列の区切りに「/」を使いますが、「/」以外の記号も同じように使えます。
これを覚えておくと、例えばファイルパスや「/」区切りの日付の一部を置換したい場合とかに、/の前にエスケープをつける必要がなくて便利です。

(例)「ccc/ddd」を「eee/fff」に置換しようとした場合
 ・「/」のまま置換(エラーになる)

$ echo "/aaa/bbb/ccc/ddd.txt" | sed -e "s/ccc/ddd/eee/fff/g"
sed: -e 表現 #1, 文字数 14: 「s」へのオプションが未知です

・「/」にエスケープをつけて置換(見づらい)

$ echo "/aaa/bbb/ccc/ddd.txt" | sed -e "s/ccc\/ddd/eee\/fff/g"
/aaa/bbb/eee/fff.txt

・「|」に変えて置換(スッキリする)

$ echo "/aaa/bbb/ccc/ddd.txt" | sed -e "s|ccc/ddd|eee/fff|g"
/aaa/bbb/eee/fff.txt

置換対象外部分に名前を付けて、置換後文字列に指定

「YYYYMMDDHHNNSS」形式の日付を「YYYY/MM/DD HH:NN:SS」に置換

$ echo "20210904130000" | sed -e "s|^(.{4})(.{2})(.{2})(.{2})(.{2})(.{2})|\1/\2/\3 \4:\5:\6|g"
 2021/09/04 13:00:00

<解説>
sedの変換対象文字列の最初の以下の部分について、一旦見やすいようにエスケープを削除してみます。

^(.{4}) 
 ↓
^(.{4})

{4}は一つ前の文字の4回の繰り返しであることを示す正規表現なので、
「.{4}」は任意の4文字を示す。
更に()で囲まれた文字列は、置換後文字列で「\1」等で表すことが可能。
※1つ目の()は\1、2つ目の()は\2みたいな感じ。

つまり、最初のコマンドラインは、まず4文字2文字2文字2文字2文字2文字に区切った後に、間に「/」とか「:」とかの区切り文字を追加するよう置換している。

vi

Linux上で使えるテキストエディタ。
環境にもよりますが、通常は「vi」を起動すると「vim」が起動します。
※違いについてはググってください。

viで編集中のファイルについて

viで編集中は、指定したファイル自体が編集されているわけではなく、「.<ファイル名>.swp」という名前のファイルが同じディレクトリ配下にされ、そちらを編集するイメージになります。

$ ll -a | grep vi
-rw-r--r-- 1 testuser testuser 12288  9月  5 13:19 2021 .vi.txt.swp
-rw-rw-r-- 1 testuser testuser    20  9月  5 12:53 2021 vi.txt

vi上で編集すると、.swpファイルの方が更新されていき、vi上で保存して終了すると実体のファイルに保存され、.swpファイルが削除されます。

viで編集中に不慮の事故でviやコンソールが落ちたりして、きちんと終了できなかった場合は.swpファイルが残ったままとなりますが、この状態でファイルを開くと以下のようなメッセージが出ます。

E325: 注意
次の名前でスワップファイルを見つけました ".vi.txt.swp"
所有者: testuser   日付: Wed Sep  5 13:19:02 2021
ファイル名: /usr/local/XRAIN/test/vi.txt
変更状態: なし
ユーザ名: testuser   ホスト名: seri
プロセスID: 41957
次のファイルを開いている最中 "vi.txt"
日付: Wed Sep  5 12:53:14 2021

(1) 別のプログラムが同じファイルを編集しているかもしれません.
この場合には, 変更をした際に最終的に, 同じファイルの異なる
2つのインスタンスができてしまうことに注意してください.
終了するか, 注意しながら続けます.

(2) このファイルの編集セッションがクラッシュした.
この場合には ":recover""vim -r vi.txt"
を使用して変更をリカバーします(":help recover" を参照).
既にこれを行なったのならば, スワップファイル ".vi.txt.swp"
を消せばこのメッセージを回避できます.

スワップファイル ".vi.txt.swp" が既にあります!
読込専用で開く([O]), とにかく編集する((E)), 復活させる((R))ecover, 削除する((D)), 終了する((Q)), 中止する((A)):

この場合は落ち着いて「E」(大文字のEなのでshift+e)を押しておけば大体大丈夫です。

ファイルの作成

$ vi <ファイル名>

→テキストエディタが開きます。

コマンドモードとインサートモードの切り替え

ファイルを開くと最初は「コマンドモード」になっています。
書き込みをするには「インサートモード」に切り替える必要がありますが、インサートモードへの切替えは以下のいずれかを実行します。

i      :カーソルのある位置から挿入開始
I(shift+i):行の先頭から挿入開始
a     :カーソルの次の位置から挿入開始
A(shift+a):行末から挿入開始
 
挿入を開始したい場所によって使い分けるも良し、どれか一つだけ覚えておいてそれだけで頑張るのも良しです(私はaしか使ったことないです)。

インサートモードからコマンドモードに切り替える場合は、エスケープキー(ESCキー)
 を押してください。

コマンドモードとインサートモードの見分け方

左下に「-- 挿入 --」と書いてあればインサートモード。
何もなければコマンドモードです。

編集内容の保存

コマンドモードで「:w」を実行するとファイルが保存されます。
引き続きインサートモードに切り替えて編集可能です。

viの終了

viを終了する時は、コマンドモードにしてから以下のいずれかを実行します。

:q   :保存せずに終了
:q!   :保存せずに強制終了(変更を反映せずに終了)
:wq   :保存して終了

矢印キーでカーソル移動できない場合

vimを開いていても、vi互換モードになっていると言うことを聞かないようです。解決方法が以下に書いてました。
https://qiita.com/CloudRemix/items/681d5b4e5eda809fecac

vi小話

viは開いたファイルの中身を全部メモリに載せるので、例えば1GBしかメモリがないマシンで1GB以上のファイルを開くとメモリ使い切ってLinuxが落ちます。
※今はもしかしたら落ちなくなってるのかもしれませんが、昔よくこれで落としてました。

viのカーソル移動

vimもしくはCentOS7以降のviであれば矢印キーでカーソル移動できます。
矢印キーが効かない場合は、コマンドモードで以下のキーでカーソル移動します。

j:下
k:上
h:左
l:右

vimの場合はカーソル使えるようになったりもするようですが、どうしてもダメな場合は上記キーを使う羽目になるので覚えておいてください。

viでの行末、行頭への移動

0もしくは^:行頭へ移動
$     :行末へ移動

正規表現と同じと思っておけば大丈夫です。

viでのページ移動

コマンドモードで以下を叩くとページ移動できます。

Ctrl+f:次のページに移動
Ctrl+b:前のページに移動

指定の行に移動

コマンドモードで以下を実行すると指定した行に移動できます。

:<行番号>

ちなみにviでファイルを開く時に以下のように指定すると、100行を開きます。編集したい行番号がわかっている時に便利です。

$ vi <ファイル名> +100

どちらの場合も、ファイルの行数よりも大きい数字を指定すると、行末になります。

ファイルの1行目に移動

コマンドモードで以下を実行すると、ファイルの1行目に移動します。

gg
もしくは
1G(Gは大文字なので「Shift+g」)

ファイルの最終行に移動

コマンドモードで以下を実行すると、ファイルの最終行に移動します。

G(Gは大文字なので「Shift+g」)

教科書に載っていない小ネタ① 編集中に別名で保存

コマンドモードで以下を実行すると、別名で保存できます。

:w <ファイル名>

ちなみにこのやり方だと一旦別名で保存されるだけで、編集中のファイルは元のファイルのままになります。

編集中のファイルごとファイル名を変えたい場合は以下を実行します。

:save <ファイル名>

教科書に載っていない小ネタ② 編集中のファイル名を確認

コマンドモードで以下を実行すると、編集中のファイル名を確認できます。
 Windowsと違ってファイル名が表示されないので、覚えておくと少し便利です。

:f

教科書に載っていない小ネタ③ 行番号を表示

コマンドモードで以下を実行すると、左側に行番号が表示されます。

:set number

カット&ペーストに使うコマンドリスト

すべてコマンドモードで使用します。
コピペ作業中はインサートモードに切り替える必要はありません。

x:1文字削除
dd:1行削除
ndd:n行削除(10ddで10行削除)
yy:1行コピー
nyy:n行コピー(10yyで10行削除)
p(小文字のp):カーソルの文字の次または次の行にペースト
P(大文字のP):カーソルの文字の前または前の行にペースト
u:カット、ペーストを取り消す

以下詳細

■ x:1文字削除

消したい文字のところにカーソルを合わせてxを押すと、1文字削除できます。
xで削除した最後の文字は、コマンドモードのままpかPを押すとペーストできます。

ちなみにインサートモードでバックスペースで消した文字も、コマンドモードに切り替えてpかPを押すとペーストできる模様(知らなかった)。

<余談>
基本的にインサートモードでバックスペースすれば普通に消せるので、わざわざxで消すことはないと思います。
ただ、本当のviだと矢印キーが使えないのと同様、バックスペースが使えない時があります。
その時はxで消すしかないので、覚えておくと便利です。

■ dd:1行削除、ndd:n行削除

行ごと消したい時は消したい行にカーソル合わせてddを押すと、1行消せます。
また、消したい行数を入力後にddを押すと、カーソル行以下を指定行数分消します。

ちなみにこれも消した行をpもしくはPで任意の行にペーストできます。

■ yy:1行コピー、nyy:n行コピー

1行丸ごとコピーしたい時は、コピーしたい行にカーソル合わせてyyを押すと、1行コピーできます。
また、コピーしたい行数を入力後にyyを押すと、カーソル行以下を指定行数分コピーします。

ちなみにこれもコピーした行をpもしくはPで任意の行にペーストできます。

p(小文字のp):カーソルの文字の次または次の行にペースト
P(小文字のP):カーソルの文字の次または次の行にペースト

<pとPの使い方の違い>
 以下の33333と22222を入れ替える場合

11111
33333
22222

まず「22222」をddで削除します。
その後pかPでペーストしますが、カーソルを合わせる行が異なります。

・小文字のpでペーストする場合

11111 ★ここにカーソルを合わせてp
33333

・大文字のPでペーストする場合

11111
33333 ★ここにカーソルを合わせてP

使ってみて気持ち悪くない方を覚えておけばいいと思います。
私は大文字のP派です。

■カット、ペーストを取消

上で説明したコマンドを実行後、取り消したい場合は小文字のuを押すと取り消せます。連打すると操作を遡って取り消します。

ちなみに私はあんまり使わないです。理由としては
 ・間違った場所をddした→そのままP押せば元に戻せる
 ・間違った場所にPした→そのままdd押せば消せる
 ・間違った場所をyyした→正しい場所でyy押すればなかったことになる

複数行コピーもしくはカットした後に間違えてペーストしちゃった時少し便利かも。

viでの検索

文字の検索方法はviewコマンドとかlessコマンドとかと同じです。
コマンドモードにしてから以下を実行します。

/<文字列>

nで後方検索、Nで前方検索できます。

viでの置換

以下すべてコマンドモードで実行します。
なお、置換を取り消したい場合は「u」で元に戻せます。

・指定の行の合致した最初の置換前文字列だけ置換

:<行番号>s/<置換前>/<置換後>

(例)10行目のabcをdefに置換

:10s/abc/def

・指定の行の合致したすべての置換前文字列を置換

:<行番号>s/<置換前>/<置換後>/g

・行の範囲を指定して置換

:<開始行番号>,<終了行番号>s/<置換前>/<置換後>/g
(例)30行目から40行目をコメントアウト
:30,40s/^/#/g

・文書内のすべての置換前文字列を置換

:%s/<置換前>/<置換後>/g

(例)すべてのabcをdefに置換

:%s/abc/def/g

・文書内のすべての置換前文字列を置換(1つ1つ確認して置換)

:%s/<置換前>/<置換後>/gc

実行すると以下のいずれかの文字の入力を求められるので、どれかを入力します。

y:カレントの文字列を置換して次へ
n:カレントの文字列を置換せずに飛ばして次へ
a:すべて置換
q:置換モードから抜ける
l、^E、^Y:なんだかよくわからなかった

検索文字列の履歴

「/」だけ打って矢印キーの上(↑)を押すと、過去に検索した文字列が出てきます。一度検索した文字列を再度検索する時に便利です。

コマンドの履歴

「:」だけ打って矢印キーの上(↑)を押すと、過去に実行したコマンドラインが出てきます。一度実行したコマンドを再実行する時に便利です。

※ただし頭に「:」を付けてコマンドラインを書いてから実行するコマンドに限ります。コピーとか削除とか(yyやdd等)には使えません。

viの取消の取消

redoの「r」を使います。あと「u」と違ってCtrlが必要です。

u:取消
Ctrl+r:取消の取消

ユーザとグループの管理

/etc/passwdについて

/etc/passwdにはユーザ一覧が記載されています。
ユーザの追加や削除や編集をすると/etc/passwdが更新されます。
ユーザ追加する時に/etc/passwdに1行追加するとユーザを追加したことになりますが、ホームディレクトリが作られなかったりするので、通常はuseraddコマンドを使った方が楽です。

/etc/passwdの書式

以下に詳しく書いてました。
http://www.wakhok.ac.jp/biblion/1997/sysadmin/node18.html

<補足1>
2つ目のフィールド(xになってたところ)は昔パスワードを書いてた時の名残らしい。実際のパスワードは暗号化されて/etc/shadowに記載されています。昔はパスワードもここで管理してたからファイル名が「passwd」のままとなっている模様。

<補足2>
最後のフィールドが「/sbin/nologin」になっているユーザは、ユーザとして存在しているだけでログインはできません。

/etc/shadowについて

<書式>
この辺参照。
http://www.wakhok.ac.jp/biblion/1997/sysadmin/node19.html

ユーザの追加・削除・変更コマンド

・追加

# useradd <ユーザ名>

オプションなしで上記を実行すると、大体以下の処理が行われます。
 ・ユーザを追加(/etc/passwdに追加される)
 ・ホームディレクトリ作成(/home/<ユーザ名>)
 ・ユーザ名と同じ名前のグループを作成(/etc/groupに追加される)

ちなみにユーザ追加後は、passwdコマンドでパスワードを設定しない限りはログインできません。
 
・削除

# userdel <ユーザ名>

オプションなしで上記を実行すると、大体以下の処理が行われます。
 ・ユーザを削除(/etc/passwdから削除される)
 ・ユーザ名と同じ名前のグループを削除(/etc/groupから削除される)

ホームディレクトリは削除されませんが、削除したい場合は「-r」オプションを付ければOK。
ちなみにホームディレクトリを削除しなかった場合、ホームディレクトリの所有者IDは誰にも紐づけられていないユーザIDとなるので、中身を参照したい場合は別のユーザを所有者として割り当てる等の作業が必要になります。

# userdel user1
# ls /home -l
total 16
drwx------ 2 testuser testuser 4096  716 13:00 testuser
drwx------ 2 penguin   users     4096 1115 09:09 penguin
drwx------ 2      1003      1002 4096 1114 09:16 user1 ★ユーザIDが割り当てられている

・変更
ユーザ情報の変更はusermodを使いますが、あんまり使わないので割愛。
大抵のことは/etc/passwdの直接編集でもできます。
ただし、ユーザ名やユーザIDを変更する場合、変更対象ユーザが所有者となっているファイルやディレクトリに関しては、usermodを使うとすべて変更後の情報に上書きしてくれますが、/etc/passewd直接編集だと変更前情報との紐づけができず、別ユーザの所有物のような感じになります。
なので、基本的にはusermodを使う方が安全です。

・直接編集した場合

# vi /etc/passwd
user1:x:1002:1002::/home/user1:/bin/bash
    ^^^★ここを1003に変更して保存
# ls /home -lt
otal 16
drwx------ 2 testuser testuser 4096  716 13:00 testuser
drwx------ 2 penguin   users     4096 1115 09:09 penguin
drwx------ 2      1002 user1     4096 1114 09:16 user ★所有者が変更前IDになる

・usermodを使った場合

# usermod -u 1003 user1ls /home -ltotal 16
drwx------ 2 testuser testuser 4096  716 13:00 testuser
drwx------ 2 penguin   users     4096 1115 09:09 penguin
drwx------ 2 user1     user1     4096 1114 09:16 user1 ★所有者がuser1

# stat /home/user1
File: ‘/home/user1’
Size: 4096            Blocks: 8          IO Block: 4096   directory
Device: 69h/105d        Inode: 21474       Links: 2
Access: (0700/drwx------)  Uid: ( 1003/   user1)   Gid: ( 1002/   user1) ★Uidは変わっている
Access: 2019-11-15 09:11:42.470628400 +0900
Modify: 2019-11-14 09:16:54.553548500 +0900
Change: 2019-11-15 09:11:42.470628400 +0900
Birth: -

ファイルの所有グループについて

ユーザは複数グループに所属させることができますが、その場合にユーザがファイルやディレクトリを作成した時の所有グループはプライマリグループになるっぽい。
以下確認。

1.testuserをtestgroup1とtestgroup2に所属させる。

 # usermod -aG  testgroup1,testgroup2 testuser

2.各グループの所属ユーザ確認

# grep testuser /etc/group
testuser:x:504:
testgroup1:x:505:testuser
testgroup2:x:506:testuser

3.プライマリグループ確認

# grep testuser /etc/passwd
# testuser:x:504:504::/home/testuser:/bin/bash
                 ^^^★プライマリグループは「testuser」

4.ファイルとディレクトリ作成

# su - testuser
$ mkdir test
$ touch testfile
$ ll
合計 4
drwxrwxr-x 2 testuser testuser 4096  925 12:54 2021 test
-rw-rw-r-- 1 testuser testuser    0  925 12:55 2021 testfile
→グループはどちらもtestuser(プライマリグループ)

5.プライマリグループを変更する

# grep testuser /etc/passwd
testuser:x:504:505::/home/testuser:/bin/bash
        ^^^★プライマリグループを「testgourp1」に変更

6.再度ファイルとディレクトリ作成

$ mkdir test2
$ touch testfile2
$ ll
合計 8
drwxrwxr-x 2 testuser testuser   4096  925 12:54 2021 test
drwxr-xr-x 2 testuser testgroup1 4096  925 12:55 2021 test2
-rw-rw-r-- 1 testuser testuser      0  925 12:55 2021 testfile
-rw-r--r-- 1 testuser testgroup1    0  925 12:55 2021 testfile2

<補足>
プライマリグループになってるグループを削除しようとすると怒られるっぽい。

# groupdel testgroup1
groupdel: ユーザ 'testuser' のプライマリグループは削除できません。

suコマンドについて

・「su」のみを実行
 変更前のユーザの環境設定を引き継ぐ(ホームディレクトリも変更前のユーザのまま)

・「-」オプションを付けて「su -」を実行
 変更後のユーザの環境設定を適用(ホームディレクトリも変更後のユーザになる)

現在のユーザに色んな環境変数を設定していて、それを変更後のユーザにも適用しておきたいの理由がない限りは「-」付きで実行したほうが無難です(引き継いでることに気づかずにハマってる人をよく見かけます)。

/etc/groupについて

/etc/groupにはグループ一覧が記載されています。
ユーザの追加や削除や編集をすると/etc/groupが更新されます。

</etc/groupの書式>

<グループ名>:<グループのパスワード>:<グループID>:<ユーザリスト>

※グループのパスワードは現在は未使用。

グループの追加・削除・変更コマンド

・追加

# groupadd <グループ名>

<削除

# groupdel <グループ名>

・変更(名前の変更)

# groupmod -n <変更前グループ名> <変更後グループ名>

ユーザとグループについて

ユーザは基本的にユーザと同じ名前のグループ(プライマリグループ)に所属します。
それ以外のグループ(セカンダリグループ)を登録することも可能です。

ユーザをセカンダリグループに加えるには以下のコマンドを実行します。

# usermod -aG <追加するグループ> <ユーザ名>

追加したいグループが複数ある場合は、カンマ区切りですべて列挙します。

逆にユーザをグループから外す場合は以下を実行します。

# gpasswd -d <ユーザ名> <グループ名>

パスワードの設定

ユーザにパスワードを設定する場合はpasswdコマンドを実行します。

# passwd <ユーザ名>

パスワードを2回聞かれるので同じものを打ち込んでください。
簡単なパスワードとかだと色々怒られたりしますが、登録できないわけではないので、気にならなければ続行して大丈夫です。

(例)

# passwd testuser
ユーザー testuser のパスワードを変更。
新しいパスワード: ★「test」を入力
よくないパスワード: 短かすぎます
よくないパスワード: 簡単すぎます
新しいパスワードを再入力してください:
# passwd testuser
ユーザー testuser のパスワードを変更。
新しいパスワード: ★「test1234」を入力
よくないパスワード: 単純/系統的すぎます
新しいパスワードを再入力してください:

これでそのユーザでログイン可能となります。
ちなみにpasswdコマンドでは、rootであれば全ユーザのパスワードを、一般ユーザであれば自分のユーザのパスワードのみ変更ができます。

ファイルの管理

chownコマンド

ファイルの所有者と所有グループを変更します。
rootのみ実行できます。

# chown <ユーザ> <ファイル・ディレクトリ> ★所有者のみ変更
# chown <ユーザ>:<グループ> <ファイル・ディレクトリ> ★所有者と所有グループのみ変更

ちなみにユーザとグループを区切るの記号は「.」でも大丈夫です。

chgrpコマンド

ファイルの所有グループを変更します。
rootのみ実行できます。

# chgrp <グループ> <ファイル・ディレクトリ>

chmodコマンド

ファイルのアクセス権を変更します。
所有者のみ実行できます。

$ chmod <アクセス権> <ファイル・ディレクトリ>


アクセス権は777等の数値で指定するのが一般的ですが、

u+rw-x,go+r-wx

みたいにも指定できます(わかりづらい)。

アクセス権について

ls -l等で見ると以下のようにアクセス権が表示されますが、それぞれ以下を示します。

-rwxrw-r--

最初の1バイト(-):ファイル種別
次の3バイト(rwx):所有者のアクセス権
次の3バイト(rw-):所有グループ(所有者を除く)のアクセス権
次の3バイト(r--) :その他のユーザのアクセス権

ちなみに所有グループのアクセス権は所有者には適用されないので、

-r--rw-rw

みたいなアクセス権の場合、所有グループが所有者を含んでいたとしても、
所有者は書き込みができません。

ファイル種別について

最初の1バイトの「ファイル種別」は以下を示します。他にもあります。
 -:普通のファイル
 d:ディレクトリ
 b:ブロックスペシャルファイル
 c:キャラクタスペシャルファイル
 l:シンボリックリンク

シンボリックリンクとハードリンクについて

Linuxにはシンボリックリンクとハードリンクがあります。
違いは以下のような感じ。

シンボリックリンク:ファイルへのショートカットみたいなイメージ
ハードリンク:ファイルそのものをリンクしているイメージ

<見た目の違い>

$ ll
合計 0
-rw-rw-r-- 2 testuser testuser 0  927 20:12 2021 hardlink ★これがハードリンク
lrwxrwxrwx 1 testuser testuser 8  927 20:12 2021 symlink -> test.txt ★これがシンボリックリンク
-rw-rw-r-- 2 testuser testuser 0  927 20:12 2021 test.txt

<inode番号の違い>

$ ll -i
合計 0
1108320 -rw-rw-r-- 2 testuser testuser 0  927 20:12 2021 hardlink
1129100 lrwxrwxrwx 1 testuser testuser 8  927 20:12 2021 symlink -> test.txt
1108320 -rw-rw-r-- 2 testuser testuser 0  927 20:12 2021 test.txt

シンボリックリンクは別のinode番号ですが、ハードリンクはinode番号も同じになります。
※完全に同じファイルであることを意味します。

<ハードリンクを更新する>

1.hardlinkを更新する

$ echo aaaaa  > hardlink

2.更新確認

$ ll
合計 8
-rw-rw-r-- 2 testuser testuser 6  927 20:14 2021 hardlink ★これが更新したファイル
lrwxrwxrwx 1 testuser testuser 8  927 20:12 2021 symlink -> test.txt
-rw-rw-r-- 2 testuser testuser 6  927 20:14 2021 test.txt ★こっちも更新かかってる

3.本体の内容を確認

$ cat test.txt
aaaaa ★更新されている。

ちなみに本体を更新してもハードリンクが更新されます。
シンボリックリンクと違って見た目は通常のファイルなので、リンクであることがわからず若干怖い物体です。

豆知識(大文字小文字の話)

ファイルやディレクトリで、Windowsでは大文字と小文字を区別しませんが、Linuxでは大文字と小文字を区別します。

なので、「TEST.txt」と「test.txt」というファイルがあった場合、Windows上では同じフォルダ内に置けませんが、Linuxでは同じディレクトリ内に置けます。

こちらは厳密にはOSによる違いではなくてファイルシステムによる違いになります。
https://qiita.com/jokester/items/f8a106832af2d3eb3436

ちなみにユーザ名とかもWindowsでは大文字小文字を区別せず、Linuxでは区別しているような気がしますが、これは認証メカニズムの違いのようです。
http://software.fujitsu.com/jp/manual/manualfiles/M080182/J2UL0530/01Z0B/J0530-00-04-04-01.html

sudoについて

sudoコマンド

sudoコマンドを使うと、一般ユーザでもroot権限で実行することができます。
通常何もしなければsudoを使うことができませんが、sudoを実行できるユーザに設定することでsudoを使うことができるようになります。

$ sudo <実行したいコマンドライン>

ちなみにこの時パスワードを聞かれますが、自分のパスワードを打ってください。
※rootのパスワードと勘違いしがちなので要注意。

<余談>
sudo使えないユーザでsudo使うと管理者に通知が飛んだりするので注意。

/etc/sudoers

sudoが使えるユーザの設定をするファイル。
以下の書式でグループやユーザを追加するとsudoが使えるようになります。

ユーザ ホスト=(権限) コマンド
 もしくは
%グループ ホスト=(権限) コマンド

「ホスト=(権限)」は大体「ALL=(ALL)」と書くことが多いです。
「コマンド」は「ALL」と書いておくとすべてのコマンドになります。
実行できるコマンドを制限したい場合はコマンドを記載します。

(例)
testuser       ALL=(ALL)       ALL ★testuserにすべてのコマンドの実行を許可
aaauser        ALL=(ALL)       /bin/chmod,/bin/chown ★aaauserにchmodとchownを許可

sudoを実行できるユーザに設定する方法

2通りあります。2つ目の方が楽なので2つ目の方で設定することが多いと思います。

1.wheelグループに加える方法

① ユーザをwheelグループに追加

# usermod -G wheel testuser

② visudo(/etc/sudoers編集用コマンド)を使って、wheelを有効にする

# visudo
 :中略
%wheel ALL=(ALL) ALL ★コメントアウトされていたらコメントを外す

2./etc/sudoersにユーザを直接追記する方法

① visudo(/etc/sudoers編集用コマンド)を使って、行を追加

# visudo
 :中略
testuser       ALL=(ALL)       ALL ★一番下あたりに追記


特殊なアクセス権限について

setuidビット、setgidビットについて

setuidビットとsetgidビットを立てておくと、実行した時にファイルの所有者の権限で実行されます。
※C言語とかで作ったバイナリのプログラム限定。シェルスクリプトの場合は効かない。

chmodで以下のように設定できます。

・setuidビットを立てる

$ chmod u+s <ファイル名>
$ ll <ファイル名>
-rwSrw-r-- 1 testuser testuser 26  96 12:41 2021 <ファイル名>

・setgidビットを立てる

$ chmod g+s <ファイル名>
$ ll <ファイル名>
-rw-rwSr-- 1 testuser testuser 26  96 12:41 2021 <ファイル名>

※実行権限(x)が付いてる時は小文字のs、ついていない時は大文字のSになるっぽい。

setuidビット、setgidビットの使いどころ

私は一度も使ったことがないのですが、例えば内部でファイルやディレクトリを作成するようなプログラムの場合、通常ならプログラムから作成されたファイルやディレクトリの所有者は実行ユーザの所有者になりますが、プログラムファイルにsetuidビットとsetgidビットを立てておくとプログラムファイルの所有者がそのままファイルの所有者になる的な感じで使うのだと思います。

sticky(スティッキー)ビットについて

スティッキービットを立てているディレクトリ配下に作成したファイルやディレクトリは、所有者にしか削除できなくなります。
※ファイルに立てても意味はなく、消したくないファイルを格納するディレクトリに立てておく必要があります。

chmodで以下のように設定できます。

$ chmod +t <ディレクトリ名>
$ ll -d <ディレクトリ名>
drwxrwxrwt 2 testuser testuser 4096 101 12:56 2021 <ディレクトリ名>

※実行権限(x)が付いてる時は小文字のt、ついていない時は大文字のTになるっぽい。

sticky(スティッキー)ビットの使いどころ

他の人に消して欲しくないファイルを格納するディレクトリに付けておくのが良さそう。

ちなみにLinuxでは/tmpディレクトリにはデフォルトでスティッキービットが立っているので、/tmp配下に作成したファイルやディレクトリは、自動的に所有者にしか削除できない状態となっています。

ファイルのアクセス権についての豆知識

umaskコマンド

ログインユーザがファイルやディレクトリを作成した時のデフォルトのアクセス権は、
umaskコマンドで変更できます。

$ umask <マスクするモード(3桁もしくは4桁)>

ファイルの場合は666から、ディレクトリの場合は777からマスクするモードが引かれ、その結果がデフォルトのアクセス権となります。

(例)002を指定した場合
 <ファイル>
  666 - 002 = 664
 <ディレクトリ>
  777 - 002 = 775

現在のマスクするアクセス権は引数無しで実行すれば確認できます。

$ umask
0002

-Sオプションを付けると、マスクするアクセス権ではなくて実際のアクセス権が表示されます。

$ umask -S
u=rwx,g=rwx,o=rx

4桁のアクセス権表現

通常アクセス権は「644」とか「777」とかの3桁で指定しますが、4桁で表現する場合があります。
大体は「0644」とか「0777」とか頭に0を付けた形になりますが、この先頭の値は以下を意味します。

setuid   4
setgid   2
sticky bit 1

なので、setuid、setgid、sticky bitを立てる場合は以下のようにもできます。

$ chmod 4644 <ファイル名>
$ chmod 1777 <ファイル名>

シェルスクリプト

シェルスクリプトの基本についてはこちらにまとめました。