基礎コマンド

echo

適当なことを表示できます。

echo hello
# hello

# 環境変数の中を見たり
echo $HOME
# /Users/hoge

less

ファイルの中身をじっくり見る。デフォルトだと監視モード(追加データがきた時に即表示)で立ち上がります。

less +F -N log.txt
# 1 foo
# 2 bar
# 3 baz
# :

監視モード時にctrl+cを押すことでファイルを読むモードになります。vimのようなキーバインドが効くので、例えばggと入力するとファイルの一番から内容を見ることができます。

  • j 1行下に
  • k 1行上に

監視モードに戻りたくなったら再度F(shift+f)を入力します。

qで終わります。

pwd

現在いるディレクトリを表示

pwd
# /Users/hoge

mkdir

ディレクトリを作る。以下はfoo/ディレクトリを作りその中にさらにbar/ディレクトリを作っています。

mkdir -p foo/bar

cd

ディレクトリ移動に使います。

  • ..は1つ上のディレクトリを
  • ~$HOMEディレクトリを
  • /から始めると一番トップ(ルートディレクトリ)

をそれぞれ指します。

pwd
# /Users/hoge

cd foo/bar
pwd
# /Users/hoge/foo/bar

cd ../..
pwd
# /Users/hoge

cd ~/foo
pwd
# /Users/hoge/foo

cd /Users
pwd
# /Users

ls

現在のディレクトリにあるディレクトリやファイルを一覧する。頭がdから始まるものがディレクトリです。

ls -al
# total 0
# drwxr-xr-x    4 nju33  staff   128 Feb  5 21:30 .
# drwxr-xr-x  109 nju33  staff  3488 Feb  5 21:30 ..
# -rw-r--r--    1 nju33  staff     0 Feb  5 21:30 index.js
# drwxr-xr-x    2 nju33  staff    64 Feb  5 21:30 node_modules

cp

ファイルやディレクトリをコピーします。

ls -al
# total 0
# drwxr-xr-x    2 nju33  staff    64 Feb  5 21:30 foo

cp -r foo bar
ls -al
# total 0
# drwxr-xr-x    2 nju33  staff    64 Feb  5 21:30 foo
# drwxr-xr-x    2 nju33  staff    64 Feb  5 21:31 bar

mv

ファイルやディレクトリを移動します。

ls -al
# total 0
# drwxr-xr-x    2 nju33  staff    64 Feb  5 21:30 foo

mv foo bar
ls -al
# total 0
# drwxr-xr-x    2 nju33  staff    64 Feb  5 21:30 bar

rm

ファイルやディレクトリを削除します。

ls -al
# drwxr-xr-x    4 nju33  staff   128 Feb  5 21:30 .
# drwxr-xr-x  109 nju33  staff  3488 Feb  5 21:30 ..
# -rw-r--r--    1 nju33  staff     0 Feb  5 21:30 index.js
# drwxr-xr-x    2 nju33  staff    64 Feb  5 21:30 node_modules

rm index.js
# ディレクトリ削除には -r が必要
rm -r node_modules

ls -al
# drwxr-xr-x    4 nju33  staff   128 Feb  5 21:30 .
# drwxr-xr-x  109 nju33  staff  3488 Feb  5 21:30 ..

grep

絞り込み。

ls -al                                                                                   
# total 0
# drwxr-xr-x    3 nju33  staff    96 Feb  5 21:41 .
# drwxr-xr-x  109 nju33  staff  3488 Feb  5 21:30 ..
# -rw-r--r--    1 nju33  staff     0 Feb  5 21:30 index.js

ls -al | grep index.js
ls -al                                                                                   
# -rw-r--r--    1 nju33  staff     0 Feb  5 21:30 index.js

cat

ファイルの中身を標準出力に出す。

cat index.js
# function hello() {
#   console.log(123);
# }
cat index.js | grep console.log
#   console.log(123);

zip ファイルを作る

-rの後に生成したい zip ファイル名が来るので注意。

zip -r name.zip ../dir-name

zip ファイルを展開する

指定した ZIP ファイルを展開します。-d <dest-dir>オプションを使うと展開先のディレクトリを<dest-dir>にできます。

unzip name.zip
unzip name.zip -d foo # foo/ に展開

pbcopy

使えるのは、 Mac だけです。標準入力できた内容をクリップボードにコピーします。

echo hello | pbcopy
# 上のあとペーストすると
hello

条件分岐

基本シンタックス

条件を[ ]で囲むか、testの後に条件を書きます。

if [ "a" = "a" ]
  then
    echo 1
  else
    echo 2
fi
# 1

改行が必要なところに;を置くことで1行で書くこともできます。

if test "a" = "a"; then echo 1; else echo 2; fi
# 1

[ ! ... ]test !のように!を付けると条件の結果が反対になります。

文字列の比較

同じ文字列

上記で使ったように=で文字列同士が等しいかチェックできます。左右の"a""a"は等しいので1が表示されます。

if test "a" = "a"; then echo 1; fi
# 1

違う文字列

違うかをチェックしたい場合は!=を使います。

if test "a" != "b"; then echo 2; fi
# 2

空文字列

空文字列('')かどうか調べるときは-n(non zero length)または-z(zero length)オプションを使います。

if test -z ''; then echo 1; fi
# 1

if test ! -n ''; then echo 1; fi
# 1

if test -n 'a'; then echo 1; fi
# 1

### 数値の比較

数値の比較では`=`は使いません。代わりに以下のようなフラグを使います。

- `-eq` 左と右が同じ数値か
- `-ne` 左と右が違う数値か
- `-lt` 左のほうが小さい
- `-le` 左は右以下
- `-gt` 右のほうが大きい
- `-ge` 右は左以上

```shell
if test 1 -eq 1; then echo 1; fi
# 1

if test 1 -ne 2; then echo 1; fi
# 1

if test 1 -lt 2; then echo 1; fi
# 1

if test 1 -le 2; then echo 1; fi
# 1

if test 2 -gt 1; then echo 1; fi
# 1

if test 2 -ge 1; then echo 1; fi
# 1

シェルスクリプトファイルの作り方

1. ファイル名

.shという拡張子を付けます。

2. 頭にshebang(シバン)コメントを付ける

#!で始まるコメントのことです。#!/bin/shと1行目に書きます。

#!/bin/sh

echo 1

これをecho.shと名付けて保存したとします。

nodejsなら#!/usr/bin/env nodeだったり色々あります。

3. 実行権限を与える

chmod +x echo.shを実行します。

4. 使う

以下のように使います。

./echo.sh
# 1

オプション付きのシェルスクリプトを書く

Mac のgetoptsではロングオプションが使えないみたいなので、そういうの使いたい場合は他の言語つかいましょう。

getopts

-a-bなどハイフンで始まる cli オプションを解析してくれるコマンドです

while getopts <条件> <変数名>; do ...; doneという書き方で書けて、すべてのオプションを繰り返し処理できます。また、このブロックの中では$OPTARGという名前でオプションの値を使うことができます。

条件

オプションでどの文字を使うか設定します。aと書くと-a単体で使うオプションだと設定でき、a:とコロンを置くと-a fooのようにオプション値が必要なオプションだと設定できます。

#!/bin/sh

show_list() {
  # 何か
  return 0
}

show_help() {
  echo 'command'
  echo '  何かコマンド'
  echo ''
  echo 'options'
  echo '  -l <arg>  一覧を表示'
  echo '  -h        ヘルプを表示'
  return 0
}

while getopts hl: option
do
  case "$option" in
    "l" ) show_list $OPTARG ;;
    "h" ) show_help ;;
  esac
done

Linux で fish shell を使う

以下をやります。

  1. ユーザーを作る
  2. Linuxbrew を入れる
  3. fish shell を入れる

ちなみにマシンはAmazonLinux2です。

ユーザーを作る

自分(ここではnju33)ユーザーを作ります。パスワードも設定しておきます。

sudo useradd -G wheel nju33
sudo passwd nju33

そして、sudoが使えるようにvisudoで以下の行のコメントを外しておきます。(#を消す)

# %wheel        ALL=(ALL)       NOPASSWD: ALL

Linuxbrew を入れる

公式サイトにかかれている以下のインストールコマンドを実行します。

sh -c "$(curl -fsSL https://raw.githubusercontent.com/Linuxbrew/install/master/install.sh)"
# ==> This script will install:
# /home/linuxbrew/.linuxbrew/bin/brew
# /home/linuxbrew/.linuxbrew/share/doc/homebrew
# ...

おそらく/home/linuxbrewができたと思います。今のところ/home/linuxbrew/.linuxbrew/bin/brewbrewコマンドを使います。

fish shell を入れる

以下でfishをインストールします。

/home/linuxbrew/.linuxbrew/bin/brew instaill fish
# ==> Installing dependencies for hello: patchelf, zlib, binutils, linux-headers, glibc, m4, gmp, mpfr, libmpc, isl@0.18 and gcc
# ==> Installing hello dependency: patchelf

そしてfishを使いたいユーザーの~/.config/fish/fish.configを以下の内容にします。まず~/.config/fish/を作ります。

mkdir -p ~/.config/fish

そしてviなどで以下のようにファイルを作ります。これでbrewとそれを使ってインストールしたものへのパスが通り使えるようになります。

set -x HOMEBREW_PREFIX /home/linuxbrew/.linuxbrew
set -x HOMEBREW_CELLAR /home/linuxbrew/.linuxbrew/Cellar
set -x HOMEBREW_REPOSITORY /home/linuxbrew/.linuxbrew/Homebrew
set -x PATH /home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin $PATH
set -x MANPATH /home/linuxbrew/.linuxbrew/share/man $MANPATH
set -x INFOPATH /home/linuxbrew/.linuxbrew/share/info $INFOPATH

デフォルトシェルを fish shell にする

そのユーザーでログインした時にfishで始められるようにします。それにはまず、/etc/shellsfishbinまでのパスを追加してあげます。

sudo viなどで/etc/shellsを開いて、以下の一行を追加します。

/home/linuxbrew/.linuxbrew/bin/fish

chsh -lで上の一行が増えていれば大丈夫です。

chsh -s [chsh -l 結果のどれか]でそのユーザーのデフォルトシェルを切り替えられます。ではfishに変えます。

chsh -s /home/linuxbrew/.linuxbrew/bin/fish
# Changing shell for nju33.
# Password: 
# Shell changed.

ここまで終わったらログインし直します。

おまけ oh-my-fish を入れる

ログインし直すと、brewfishなどが使えるようになっていると思います。(もし、使えない場合は.config/fish/config.fishを確認してください)

fishが使えるのでoh-my-fish推奨の以下でただ入れることができます。

curl -L https://get.oh-my.fish | fish

僕が好きなedenテーマも入れます。

omf install eden
# Updating https://github.com/oh-my-fish/packages-main master... Done!
# Installing package eden
# ✔ eden successfully installed.

デフォルトシェルを変更する

shellbin自体は何らかの方法で取得済みという状態だとします。ここの例ではfishをデフォルトシェルに変更してみます。

/etc/shellsへ追記

そのbinへのパスを/etc/shellsへ追記します。
ここではそのパスは/home/linuxbrew/.linuxbrew/bin/fishになるのでこれを追記します。

sudo vi /etc/shells
# /home/linuxbrew/.linuxbrew/bin/fish と追記

追記内容の確認

以下で確認できます。

chsh -l
# /bin/sh
# /bin/bash
# /sbin/nologin
# /bin/dash
# /home/linuxbrew/.linuxbrew/bin/fish

シェルの変更

chsh -l [切り替えたいシェルの bin] を実行します。binchsh -lからコピペするといいと思います。
fishへ切り替えます。

chsh -l /home/linuxbrew/.linuxbrew/bin/fish

再ログイン

一度そのユーザーから離れて再ログインするとfishで入れると思います。

ユーザーの作成と削除

デフォルト値の確認

-Dを付けて実行すると、対象のオプション(ホームディレクトリ設定)などを指定しなかった場合、何が設定されるのかを確認できます。

useradd -D
# GROUP=100
# HOME=/home
# INACTIVE=-1
# EXPIRE=
# SHELL=/bin/sh
# SKEL=/etc/skel
# CREATE_MAIL_SPOOL=no

例えば、ホームディレクトリを変えたい場合は-m-dオプションを、デフォルトシェルを変えたい場合は-sオプションを作る時に指定してあげます。

ユーザーを作る

sudo useradd <user-name>

# -m で ホームディレクトリ $HOME/<user-name> を作り
# -s で デフォルトシェル /usr/bin/fish を指定
# sudo useradd <user-name> -m -s /usr/bin/fish

以下のコマンドで表示されれば追加できていると確認できます。

ユーザー確認

cat /etc/passwd | grep <user-name>

ここで「ユーザー名間違えてた」って時は、

sudo usermod -l <current-name> <new-name>

で変更できます。もし、ホームディレクトリも作ってしまっているなら、

sudo usermod -l ... -d /home/<new-name> -m

上記のように一緒に-d-mを付けることで変更できます。

sudoer にする

/etc/sudoers/<user-name>を作ります。

<user-name> ALL=(ALL) ALL

上だとsudoを付けた時に毎回パスワードを入力する必要がある設定です。パスワードを省きたい場合は以下も追記します。

<user-name> ALL=(ALL) NOPASSWD:ALL

ちなみにこのファイルは、/etc/sudoersにこう書かれていることでsudoers.d内に作ったファイルが読み込まれるようです。

#includedir /etc/sudoers.d

ユーザーのパスワードを設定する

sudo passwd <user-name>
# Changing password for user test.
# New password: ***
# Retype new password: ***

シェルスクリプトなどで標準入力などで渡したい場合は次のように設定します。

echo <password> | passwd <user> --stdin

ubuntu ではpasswdの代わりにchpasswdを使います。

echo <user>:<password> | chpasswd

ユーザーを削除する

--remove -rを付けるとそのユーザーのホームディレクトリも削除します。

sudo userdel <user-name>
# sudo userdel --remove <user-name>

ssh できるようにする

ローカル側

まずはsshするときに使う鍵を作ります。これにはssh-keygenコマンドを使います。

# ~/.ssh/test/id_rsa に置きたいので作っておきます。
mkdir ~/.ssh/test

ssh-keygen -f ~/.ssh/test/id_rsa -N '' -t rsa -b 4096 -m PEM -C ''

ssh-keygenには5つオプションを渡していますが、これはそれぞれ

  • -fは鍵のベース名
  • -Nは鍵のパスワード
  • -tは鍵のタイプ
  • -bは鍵のサイズ
  • -mは鍵の形式(よく分かってません)
  • -Cは公開鍵の後ろに付くuser@host部分

を指定します。2019年2月ぐらいからこの-m PEMというオプションを付けないと-----BEGIN OPENSSH PRIVATE KEY-----と出力されてしまいうまくsshできないようなので付けるように覚えないと駄目みたいです。
ちなみにこのオプションを付けるとそれ以前と同じように-----END RSA PRIVATE KEY-----で出力されます。

これで~/.sshディレクトリにmy-key(秘密鍵)とmy-key.pub(公開鍵)ができたはずです。

そして、その公開鍵をコピーします。

cat ~/.ssh/test/id_rsa.pub | tr -d '\n' | pbcopy 

tr -d '\n'で最後の改行を消す必要があります。(ハマった)

リモート側

使いたいユーザーの~/.ssh/authorized_keysにコピーしたものを貼り付けます。

# .ssh ディレクトリを作る
mkdir ~/.ssh
# パーミッションを 600(自分だけが読み書き実行できる) に
chmod 700 ~/.ssh

# authorized_keys を作る
touch ~/.ssh/authorized_key
# パーミッションを 600(自分だけが読み書きできる) に
chmod 600 ~/.ssh/authorized_key

接続できるか確認

以下のようなコマンドで接続できれば完了です。

ssh -i ~/.ssh/test/id_rsa <user>@<host>
# [<user>@ip-<host> ~]$

接続できない時のチェック項目

リモート

  1. "$HOME/.ssh"のパーミッションが700
  2. "$HOME/authorized_keysのパーミッションが600
  3. authorized_keysは空白行で終わってるか(<id_rsa.pubの中身>\nみたいな形)

ローカル

  1. 秘密鍵のパーミッションが600または400

ローカルの鍵情報をリモートでも引き継ぐ

GitHubへ普段からローカルでsshを使った方法でpushしたりcloneしている場合、リモート先からだと秘密鍵がないのでそういったことができなくなります。

そういうときはssh-addコマンドを使います。以下で登録できます。

ssh-add <リモートで必要な秘密鍵へのパス>

ssh-add -l
# 登録できていればここにそれが出る

あとはssh時に-Aオプションを付けてあげるだけです。

ssh -A ...

リモート先でもssh-add -lして出てくれば完璧です。

パスワードが必要な sudo だけど聞かれないようにする

そういう設定がされてないとsudoコマンドを実行した時にパスワードを入力しないといけません。

sudo echo 1
# Password:

普通にターミナルでそれだけ実行する程度ならそれでも特に問題ないのですが、シェルスクリプトなどで処理の流れを止めたくない時にそれはちょっと困ります。

sudo -S

そんなときは-Sまたは--stdinオプションを使います。これでパスワードが標準入力で渡すことができます。

-S, --stdin read password from standard input

例えばこんな感じです。

echo 'passwd' | sudo -S echo 1 2>/dev/null
# 1

2>/dev/nullは、sudoを実行したときのPassword:という標準エラー出力のテキストが表示されないように/dev/nullに捨てています。

fish shell でバージョン Bump する function

リポジトリはnju33/fish-age

インストール

以下でage(読み「あげ」)をインストールします。

mkdir -p ~/.config/fish/{functions,completions}
curl -o ~/.config/fish/functions/age.fish https://raw.githubusercontent.com/nju33/fish-age/master/functions/age.fish
curl -o ~/.config/fish/completions/age.fish https://raw.githubusercontent.com/nju33/fish-age/master/completions/age.fish

ここまで終わったらfishを再読込します。

使い方

バージョンを直接書く

バージョンを書いた後に--patch,--minor,--majorのどれかを指定します。ちなみに、

  • --patchは最後の数値をインクリメント
  • --minorは真ん中の数値をインクリメント
  • --majorは最初の数値をインクリメント

するという意味になります。

age v1.2.3 --patch
# v1.2.4

age v1.2.3 --minor
# v1.3.0

age v1.2.3 --major
# v2.0.0

gitのタグからバージョンを自動取得

バージョンを記した Git タグがある場合、コマンドのバージョンを省略するとタグから最新のバージョンを取得し、指定したオプションに合わせて Bump したバージョンを表示します。

# 以下のようなタグの状態のとき
git tag
# v1.1.0
# v1.2.0
# v1.2.1
# v1.2.2
# v1.2.3

age --patch
# v1.2.4

age --minor
# v1.3.0

age --major
# v2.0.0

sed で n行目以降だけ表示

sed -n 2pとかすると1行目だけ取得できますが、2行目以降だけ取得したい場合はどうしたらいいだろうということでした。

結論としてはこのように実行するでした。

sed -n '2,$p'

$pというのは最後の行を指すようです。つまり2行目から最後の行というような意味になるんでしょうか。

パイプ処理の進歩を表

これはpvコマンドに依存します。Mac に人は Homebrew を使って以下でインストールできます。

brew install pv

pv コマンド

pvコマンドはcatのようにファイルの内容を表示することができます。そしてそれを別コマンドでパイプ(標準入力)で渡すことで、どれだけ処理が進んだかをプログレスで表示してくれます。

MySql のリストアの進歩を表示

以下のようにするとa.dumpのリストアの進歩がどれくらいか見ることができます。

pv a.dump | mysql --defaults-extra-file=db.cnf db_name
# 400MiB 0:02:18 [2.89MiB/s] [=========================================>] 100%

tree コマンド

treeコマンドは誰かにディレクトリ・ファイル構造を見せて説明したりする時に便利なコマンドです。

コマンドの使い方を見る前に以下で現在のワーキングディレクトリをベースに適当な構造を作ります。

mkdir -p {a,b}/c
touch {a,b}/c/d.{txt,log,js,css,html}

では構造を表示してみます。ワーキングディレクトリをベースとする場合、単にtreeと実行します。

tree
# .
# ├── a
# │   └── c
# │       ├── d.css
# │       ├── d.html
# │       ├── d.js
# │       ├── d.log
# │       └── d.txt
# └── b
#     └── c
#         ├── d.css
#         ├── d.html
#         ├── d.js
#         ├── d.log
#         └── d.txt

もしa/cをベースで表示したい場合はtree a/cとコマンドの後に起きます。

tree a/c
# a/c
# ├── d.css
# ├── d.html
# ├── d.js
# ├── d.log
# └── d.txt

treeには他にフィルターのようなオプションがいくつかあります。

フィルターオプション

-P

-P <pattern>オプションを使うと、パターンにマッチしたファイル名以外は非表示にできます。

tree -P '*.js|*.css' a/c  
# a/c
# ├── d.css
# └── d.js

-I

-I <pattern>-Pの逆で、マッチしたものが非表示になります。

tree -I '*.js|*.css' a/c
# a/c
# ├── d.html
# ├── d.log
# └── d.txt

-L

-L <number>を使うと何階層目まで表示するかを確認します。

tree -L 1
# .
# ├── a
# └── b

-L 1で1階層目だけなのでabが表示されます。

プロセス置換: <(...) & >(...)

プロセス置換という技を使うと、本来ファイルを指定して実行してその中身を使うようなコマンドで、直接文字列を渡すといったことができるようになります。<(...)が入力、>(...)が出力向きになります。

🤔色々と憶測ですが、僕的にはこれは戻り値がファイルのように扱えるワンライナーな関数みたいなイメージでかなと思ってます。

まずLinux 上でstat -c%N /dev/fd/*でどんな/dev/fs/*があるか見ることができます。例えばその中には'/dev/fd/0' -> '/dev/pts/9'というのがあるんですが、この時ターミナル1で、

cat /dev/fd/0

とし、ターミナル2でecho 123 > /dev/pts/9を実行するとターミナル1に123と流せるというような仕組みがあります。echo >(echo)'などと実行する時も/dev/fd/63という結果がでてくるので、どこかからの標準出力が/dev/fd/63に送られてきたりしてるんだろうなと。

これがファイルの代わりとして扱えるのは/dev/fd/63が実際にコマンドが生きてる間存在するファイルか何かなんだと思います。cat /dev/nullが使えるように、(内容が渡ってくる点だけ異なる)ファイルであるならファイルを期待する箇所におけるのはおかしくないかなと。

(⚠️この機能はshでは使えない為、bashを使う必要があります。)

例えばファイルを期待するコマンドの有名なものにcatがあります。これはcat <file-path>でそのファイルパスの内容を表示するコマンドですが、プロセス置換を使うと、

bash -c 'cat <(echo -n 123)'
# 123

<(...)の中の実行結果を表示することができます。この使い方ではechoと変わらないですが、例えばbase64コマンドなどだと少し便利さを感じられます。(これもbase64 <file-path>を期待)

bash -c 'base64 <(echo -n username:password)'
# dXNlcm5hbWU6cGFzc3dvcmQ=

またdiffなどもその類です。

bash -c 'diff <(echo aaa) <(echo abb)'
# 1c1
# < aaa
# ---
# > abb

>(...)にすると前のコマンドの結果をその中のコマンドに続けて渡すことができます。

bash -c 'echo 123 > >(cat)'
# 123