20200210のLinuxに関する記事は10件です。

LinuxのModemManagerが自作キーボードへのキーマップ書き込みを邪魔している時の対処法

背景

久しぶりに自作キーボード(ErgoDash mini)のキーマップをいじろうとしたらハマったため共有です。
ErgoDash miniに限らず、Pro Microを使用した自作キーボード(というよりはArduino互換ボードを使用したデバイス全般)をLinux環境で扱う際に発生しうる事象です。

事象としては、QMK Firmwareをクローン済のディレクトリにて、以下のコマンドでキーマップの書き込みを行おうとしたところ、

$ sudo make ergodash/mini:[キーマップのディレクトリ]:avrdude

以下のようなエラーが発生し書き込みに失敗しました。

avrdude: Error: butterfly programmer uses avr_write_page() but does not
provide a cmd() method.

発生時の環境情報は以下の通り。

$ uname -vrpm
4.15.0-76-generic #86-Ubuntu SMP Fri Jan 17 17:24:28 UTC 2020 x86_64 x86_64

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.3 LTS"

とりあえず結論

LinuxのModemManagerの割り込みによってキーマップの書き込みが失敗していました。
以下の通りModemManagerを停止させた後、書き込みを再実行して成功しました。
※必ず、ModemManagerを利用していないことを確認してから実行してください。モバイルブロードバンド接続していたら怪しいので、踏みとどまりましょう。

$ sudo systemctl stop ModemManager.service

解説

ModemManagerは、モバイルブロードバンドでの接続をよしなに制御してくれるデーモンです。
モバイルブロードバンド接続で利用するモデムは、USB接続のものが多く存在します。
そのため、当然ですが、ModemManagerはUSBポートを開くことができます。
キーボード(というよりも、Pro Micro)が接続されているUSBポートも例外ではなく、キーマップ書き込み処理中にModemManagerによる割り込みが発生すると処理が失敗します。

有名な問題のようで、QMK Firmwareのリポジトリでも対応するためのissueが起票されています。
見たところ、対応策は「フラッシュ処理を実行する際にModemManagerが実行中だったら警告を出す」といったものになりそうなので、やらなきゃいけないことは変わらなさそうです。

停止したModemManagerは、利用しないのであればそのまま放置で問題ないです。
絶対に利用しないということであれば、以下コマンドで自動起動設定を無効化しても良いでしょう。

$ sudo systemctl disable ModemManager.service

「そうはいっても、起動していたものだから、元に戻したいな...」ということであれば起動し直しても良いでしょう。

$ sudo systemctl start ModemManager.service

参考にしたページ

Helix キーボードキットを組み立てた - FOHTE.NET
avrdude: Error: butterfly programmer uses avr_write_page() but does not provide a cmd() method - Stack Exchange
#877024 modemmanager should ask before messing with serial ports - Debian Bug report logs

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GPD P2 Max Ubuntu Mateインストールメモ

インストール前

http://gpd.hk/gpdp2maxfirmwaredriverbios
実行するまえにBIOSのアップデートを行う
公式ツールはWindows用
アップデートしないとタッチパネルが認識しない

インストールについて

https://ubuntu-mate.org/download/
ISOイメージからUSBメモリ起動ディスクを作るツールで
起動メモリを作る
あとは、ガイダンス通り実行する

タッチパネルが狂っている場合

xinputコマンドを実行
Goodix Capacitive TouchScreen id=16 [slave pointer (2)]

が表示されるので、
xinput set-prop '16' 'Coordinate Transformation Matrix' 0 -1 1 1 0 0 0 0 1
を実行すれば、タッチパネルの位置は修正できる。
自動起動するアプリに登録すれば、治った状態で起動できる

xinput set-prop `xinput|grep Goodix|grep pointer|awk -F'id=' '{print $2}'|awk '{print $1}'` 'Coordinate Transformation Matrix' 0 -1 1 1 0 0 0 0 1
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LVMを使って複数のディスクを1つに統合する

0. 概要

ここ最近、メディア機器の高機能化に伴い、画像や動画等容量の大きいものが増えてきている。
様々なHDDにデータを保存していると、どのHDDにどのデータがあるのか見つけにくくなってしまう問題がある。
そこで複数のHDDを束ねて、1つの大容量HDDとして、取り扱いたいと考える。
今回は、複数のHDDをLVMを用いて束ねる方法を説明する。

1. 前提条件

  • HDDは全てで5台

  • 各HDDは8TB

  • 2つのグループに容量を統合する

2. ディスクの統合

2.1. ディスクの確認

まず以下からディスク情報を調査して、統合するディスクの名前を確認する。

$ sudo fdisk -l

# ------------------------------------------------------------------ #
Disk /dev/sdf: 7.3 TiB, 8001563222016 bytes, 15628053168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/sdg: 7.3 TiB, 8001563222016 bytes, 15628053168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/sdh: 7.3 TiB, 8001563222016 bytes, 15628053168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/sdd: 7.3 TiB, 8001563222016 bytes, 15628053168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/sde: 7.3 TiB, 8001563222016 bytes, 15628053168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
# ------------------------------------------------------------------ #

他のHDDと間違えていないか、製造番号の確認もしておく。

$ sudo smartctl -a /dev/sde | grep Model

# ------------------------------------------------------------------ #
Device Model:     ST8000VN004-2M2101
# ------------------------------------------------------------------ #

マウントされていないかも確認しておく。

$ df -h

もし、マウントされていたら以下で解除

$ umount <YOUR MOUNT POINT>

2.2. ディスクの初期化

ディスク5台を初期化

$ sudo pvcreate /dev/sdd
  WARNING: ext4 signature detected on /dev/sdd at offset 1080. Wipe it? [y/n]: y
  Wiping ext4 signature on /dev/sdd.
  Physical volume "/dev/sdd" successfully created.

同様にsde, sdf, sdg, sdhも行う。

2.3. ディスクの統合(グループ化)

次に初期化したディスクを統合していく。
今回はsdd, sde, sdf, sdg, sdh8TBディスク5枚
3枚で1つ2枚で1つ、すなわち24TB16TBの論理ドライブに
統合して構築する。

まず24TBの方から統合。

$ sudo vgcreate exthd1Group /dev/sdd /dev/sde /dev/sdf

# ------------------------------------------------------------------ #
Volume group "exthd1Group" successfully created
# ------------------------------------------------------------------ #

同様に、16TBの方も行う。

$ sudo vgcreate exthd2Group /dev/sdg /dev/sdh

2.4. ディスクの統合(ボリュームの作成)

次に、ロジカルボリュームを作成する。
上記で作成したHDDグループのexthd1Groupを指定して、ロジカルボリュームを作成する。

$ sudo lvcreate -l 100%FREE -n lv0 exthd1Group

# ------------------------------------------------------------------ #
  Logical volume "lv0" created.
# ------------------------------------------------------------------ #

同様に、exthd2Groupについても行う。

$ sudo lvcreate -l 100%FREE -n lv0 exthd2Group

なお、全ての容量を使い切らないのであれば、-l 100%FREE-L 8T等にすればよい。
lv0はロジカルボリュームの名前を指している。

2.5. ディスクの統合(ボリューム確認)

作成したボリュームを確認する。

$ sudo lvdisplay
# ------------------------------------------------------------------ #
  --- Logical volume ---
  LV Path                /dev/exthd2Group/lv0
  LV Name                lv0
  VG Name                exthd2Group
  LV UUID                
  LV Write Access        read/write
  LV Creation host, time N/A, 2020-02-10 14:09:41 +9999
  LV Status              available
  # open                 0
  LV Size                14.55 TiB
  Current LE             3815442
  Segments               2
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:3

  --- Logical volume ---
  LV Path                /dev/exthd1Group/lv0
  LV Name                lv0
  VG Name                exthd1Group
  LV UUID                
  LV Write Access        read/write
  LV Creation host, time N/A, 2020-02-10 14:09:35 +9999
  LV Status              available
  # open                 0
  LV Size                21.83 TiB
  Current LE             5723163
  Segments               3
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           253:2
# ------------------------------------------------------------------ #

後はフォーマットして使うだけである。

2.6. ディスクの統合(ボリュームの初期化)

各HDDグループのボリュームに対して、ext4でフォーマットする。まずexthd1Groupのボリュームに対して。

$ sudo mkfs -t ext4 /dev/exthd1Group/lv0

同様に、exthd2Groupのボリュームに対して。

$ sudo mkfs -t ext4 /dev/exthd2Group/lv0

2.7. ディスクの統合(マウント)

まずは作成し初期化したボリュームを確認する。

$ sudo fdisk -l

# ------------------------------------------------------------------ #
Disk /dev/mapper/exthd1Group-lv0: 21.9 TiB, 24004685463552 bytes, 46884151296 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes


Disk /dev/mapper/exthd2Group-lv0: 14.6 TiB, 16003123642368 bytes, 31256100864 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
# ------------------------------------------------------------------ #

しっかり出来ている。では、これらをマウントする。

$ sudo mount   /dev/exthd1Group/lv0 ./hdd1
$ sudo mount   /dev/exthd2Group/lv0 ./hdd2

もし、起動時に自動マウントさせるなら各統合HDDグループのボリュームのUUIDを確認してfstabに記述する。
まず、UUIDの確認。

$ sudo blkid /dev/exthd1Group/lv0

# ------------------------------------------------------------------ #
/dev/exthd1Group/lv0: UUID="7ii63fbf-deed-4ff1-b4af-8156f" TYPE="ext4"
# ------------------------------------------------------------------ #

同様にexthd2Groupも確認。

$ sudo blkid /dev/exthd2Group/lv0

# ------------------------------------------------------------------ #
/dev/exthd2Group/lv0: UUID="8ii63fbf-deed-4ff1-b4af-8156f" TYPE="ext4"
# ------------------------------------------------------------------ #

最後にfstabに記述する。

$ sudo vim /etc/fstab

# ------------------------------------------------------------------ #
追記
UUID=7ii63fbf-deed-4ff1-b4af-8156f     /hdd1    ext4   defaults        0 3
UUID=8ii63fbf-deed-4ff1-b4af-8156f     /hdd2    ext4   defaults        0 3
# ------------------------------------------------------------------ #

99. グループ及びPVを削除する

pvcreateを行う際に既にLVMを使った痕跡がある場合は、

$ sudo pvdisplay

で既存のディスクを確認して、PV及びそのPVがどのグループに属しているかを見る。
もしUnkownになっている場合、グループを作った後に物理デバイスが抜かれた可能性が高い。

一貫性を持たせるために以下を実行してUnkownを削除する。

$ sudo vgreduce --removemissing <YOUR_GROUP>

次にグループを削除。

$ sudo vgremove <YOUR_GROUP>

PVを削除。

$ sudo pvremove <YOUR_PV>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Raspberry Piを公開鍵認証で接続できるようにする

はじめに

Raspberry PiにLAN外から接続できるようにするに当たり、パスワード認証で接続できるようにしておくのは不安なので、本記事ではRaspberry Piにsshで接続する際にパスワード認証ではなく公開鍵認証で接続できるようにすることを目的としています。
同様の記事は数多ありますが、自分の詰まった点なども備忘録的にまとめました。
ここではRaspberry Piはセットアップ済みの状態から始めます。

環境

使ったもの

Raspberry Pi 3 Model B
Raspbian Buster Lite(Webサーバ用途なのでLiteで十分です)
macOS Catalina

使わなかったもの

ディスプレイ
キーボード

すべてsshのみで設定しました。

おおまかな流れ

  1. 接続用PCで公開鍵を作成する
  2. 公開鍵をRaspberry Piにscpで転送し、登録する
  3. パーミッションの変更をする
  4. sshでパスワード認証できないようにする

接続用PCで公開鍵を作成する

接続するPCで以下のコマンドを実行します。
macOSの場合はターミナルにsshクライアントが導入されているのでそのまま打ち込めば大丈夫です。
Windowsの方はCygwinなどを用いて実行しましょう。

$ ssh-keygen -t rsa

これにより秘密鍵と公開鍵がRSAという方式で生成され、これをどこに保存するかが以下のように聞かれます。

Generating public/private rsa key pair.
Enter file in which to save the key (/home/username/.ssh/id_rsa):

特にこだわりがなければEnterを押すことでデフォルトの/home/username/.sshに保存されます。
普通に使用する分にはそのままで良いでしょう。
Enterを押すと、公開鍵認証で接続する際に用いるパスフレーズを登録するように言われます。

Enter passphrase (empty for no passphrase):

私の環境ではセキュリティ的に問題がないと思ったので何も入力せず(登録せず)そのままEnterを押しました。
再度入力するよう求められますので、パスフレーズを登録した方はそれを入力します。

Enter same passphrase again:

ここまでで以下のようにメッセージが表示され、秘密鍵と公開鍵の作成は終わりとなります。

Your identification has been saved in /home/username/.ssh/id_rsa.
Your public key has been saved in /home/username/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx username@hostname
The key's randomart image is:
+---[RSA 2048]----+
|                 |
|                 |
|                 |
|                 |
|                 |
|                 |
|                 |
|                 |
|                 |
+----[SHA256]-----+

公開鍵をRaspberry Piにscpで転送し、登録する

sshの機能の一つであるscpを用いて、先程作成した公開鍵をRaspberry Piに転送し、その内容を登録します。

以下のコマンドを接続するPCで実行します。

$ scp -P 22 .ssh/id_rsa.pub pi@192.168.10.x:

私の環境では接続するPC(MacBook)でのデフォルトのディレクトリが[Username]なので、その直下に.sshディレクトリがあります。
そのため上記のコマンドで.sshにあるid_rsa.pubをpi@￰192.168.10.xに転送できますが、そうではない方は適宜id_rsa.pubのパスとRaspberry PiのIPアドレスを読み替えてください。

コマンドを入力すると、Raspberry Piのパスワードを聞かれるので、入力してEnterを押します。

pi@192.168.11.x's password:

これで公開鍵を転送できたので、次はRaspberry Piに転送した公開鍵を登録します。
登録というのは、Raspberry Piにある.sshディレクトリのauthorized_keysというファイルに公開鍵の内容を追記することです。
初回は.sshディレクトリさえ存在しないので、追記ではなく新規作成という形になります。

Raspberry Piに公開鍵の登録先である.sshディレクトリを作成しておきます。
Raspberry Piにpiユーザーでログインし、ターミナルで以下のコマンドを実行します。

$ mkdir .ssh

デフォルトのディレクトリ下に.sshディレクトリが作成されます。
この中にauthorized_keysというファイルが欲しいので、公開鍵の内容を書き込んだファイルを作成するため、以下のコマンドを実行します。

$ cat id_rsa.pub > .ssh/authorized_keys

これで公開鍵が登録されたauthorized_keysファイルが作成されますが、いくつか注意点があります。

1つ目は、ふつう公開鍵を登録する際は「上書き」ではなく「追記」という形になる点です。
今回は公開鍵を登録するのが初回だったため上記のコマンドでよかったのですが、普段は追記なので
$ cat id_rsa.pub >> .ssh/authorized_keys
となる点に留意してください。

2つ目は、/etc/ssh/sshd_configの編集により、sudoコマンドを用いないとcatが使えない場合です。
この場合、
$ sudo cat id_rsa.pub
まではsudoが適用されますが、以降のリダイレクトの
> .ssh/authorized_keys
の部分についてはsudoが適用されたないため、permission deniedのエラーが起きます。
この時は以下のコマンドを使いましょう。
$ sudo sh -c "cat id_rsa.pub > .ssh/authorized_keys"
これでおそらくauthorized_keysファイルの作成ができると思います。

authorized_keysに公開鍵を登録できたら、id_rsa.pubはもう不要なので、以下のコマンドで削除しましょう。

$ rm id_rsa.pub

パーミッションの変更をする

ディレクトリやファイルにパーミッション(権限)が正しく設定されていないと、sshで接続できません。そのため適切に権限を変更します。

以下のコマンドをRaspberry Piのターミナルで実行します。

$ chmod 700 .ssh
$ chmod 600 .ssh/authorized_keys

ここで、sudoで.sshディレクトリを作った方やauthorized_keysファイルを作った方は、これらの所有者がrootになっている可能性があります。
もしそうであれば以下のコマンドで所有者をpiに変更しておきましょう。

$ chown pi:pi .ssh
$ chown pi:pi .ssh/authorized_keys

所有者や権限の確認は$ ls -laでできます。

ここまでで公開鍵認証での接続ができるようになったかと思います。

sshでパスワード認証できないようにする

Raspberry Piのターミナルでホームディレクトリに戻ってから以下のコマンドを実行します。

$ sudo vi /etc/ssh/sshd_config

Vimエディタでsshd_configファイルを開くので、PasswordAuthenticationの項目をyesからnoに変更しましょう。
Vimの使い方がわからない人はnano等で適当に開いてみてください。nanoは操作方法が画面下部に記載されています。

/etc/ssh/sshd_config
PasswordAuthentication no

編集したらsshサービスを再起動しましょう。
bash
$ /etc/ssh/sshd_config

以上で公開鍵認証で接続できるようになったと思います。
接続できるか確認してみましょう。
接続用のPCのターミナルで以下のコマンドを実行します。

$ssh -i .ssh/id_rsa -p 22 pi@192.168.10.xx

接続用PCのid_rsaの場所やポートによって適宜.ssh/id_rsaなどは読み替えてください。
うまく以下が表示されたら完了です。

pi@raspberrypi:~ $

おわりに

私の環境では公開鍵を転送し登録した後、sshで何度接続しようとしてもPermission denied(Public key)のエラーが出て接続できませんでした。
この際パスワード認証も切っていたために、泣く泣くキーボードとHDMIケーブルをラズパイに挿して設定を見直していました。
結局は公開鍵と秘密鍵のペアの違いで、公開鍵を作成し直したら接続できましたが、このような初歩的なミスもあり得るので皆さんは気をつけてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Automotive Grade Linux(AGL)

「Linuxでこれができる」だけじゃない、車載Linuxのデモから見えた実用性へのこだわり
https://monoist.atmarkit.co.jp/mn/articles/2002/06/news059.html?fbclid=IwAR2IGj6zisyVofRKcx5Lkum_A_t1wdePDKvLdYtWEhCu9inIg2QQgcda1A0

しまったAutomotive Grade Linux(AGL)資料整理していない。

https://www.automotivelinux.org/

Document

The Automotive Grade Linux Software Defined Connected Car Architecture
20th June 2018 Final
https://www.automotivelinux.org/wp-content/uploads/sites/4/2018/06/agl_software_defined_car_jun18.pdf

2018 Global In-Vehicle Infotainment
Enabling Technology Leadership Award
https://www.automotivelinux.org/wp-content/uploads/sites/4/2019/05/Frost-Sullivan-Automotive-Grade-Linux-Award-Write-Up.pdf

The Linux Foundation.
https://www.linuxfoundation.org/

Wiki
https://wiki.automotivelinux.org/

Git Repositories
https://git.automotivelinux.org/

docker-worker-generator

https://git.automotivelinux.org/AGL/docker-worker-generator/

Dockerfile
FROM debian:10

COPY INSTALL /root/INSTALL
RUN /root/INSTALL/setup_image.sh

ENTRYPOINT ["/usr/bin/wait_for_net.sh"]
CMD ["/bin/systemd"]

setup_image.sh

setup_image.sh
#!/bin/bash -x

### this script is called when building the docker image: it's executing in a temp container

echo "------------------------ Starting $(basename $0) -----------------------"

function debug() {
    set +x
    echo "-------------------------"
    echo "Command failed."
    echo "Running sleep for 1 day. To proceed:"
    echo "* run 'killall sleep' to continue"
    echo "* run 'killall -9 sleep' to abort the build"
    sig=0
    sleep 86400 || sig=$?
    # if killed -9, then abort
    [[ $sig == 137 ]] && exit 1 # abort
    set -x
    return 0 # continue
}

function crash() {
    set +x
    echo " -------------------------"
    echo "| CRASH HANDLER TRIGGERED |"
    echo " -------------------------"
    exit 1
}

# get the INSTALL dir (the one where we were launched)
INSTDIR=$(cd $(dirname $0) && pwd -P)
echo "Detected container install dir = $INSTDIR"

if [[ -f $INSTDIR/DEBUG ]]; then
    echo "#### INTERACTIVE DEBUG MODE IS ACTIVE ####"
    trap debug ERR # on error, run a sleep tp debug container
else
    echo "#### JENKINS MODE MODE  IS ACTIVE ####"
    trap crash ERR # on error, simply crash
fi


################################## variables #################################

# source variables in conf file
. $INSTDIR/image.conf

# source flavour config file (generated by top Makefile)
. $INSTDIR/flavour.conf

if [[ -z "$CONTAINER_TYPE" ]]; then
    grep -q docker /proc/self/cgroup && CONTAINER_TYPE="docker"
fi

################################## install docker endpoint #####################

# install the entrypoint script in /usr/bin
install --mode=755 $INSTDIR/wait_for_net.sh /usr/bin/

################################## install first-run service ###################
# all operations requiring runnning daemons (inc. systemd) must be run at first
# container instanciation

if [[ "$FIRSTRUN" == "yes" ]]; then
    install --mode=755 $INSTDIR/firstrun.sh      /root/firstrun.sh
    install --mode=644 $INSTDIR/image.conf       /root/firstrun.conf
    [[ -d $INSTDIR/firstrun.d ]] && cp -a $INSTDIR/firstrun.d /root/
    mkdir -p /etc/systemd/system/multi-user.target.wants/
    cat <<EOF >/etc/systemd/system/multi-user.target.wants/firstrun.service
[Unit]
Description=Firstrun service
After=network.target

[Service]
Type=oneshot
ExecStart=-/bin/bash -c /root/firstrun.sh
TimeoutSec=0
StandardInput=null
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF
fi

# helper func to install a firstrun hook
function firstrun_add() {
    script=$1
    level=${2:-50}
    name=${3:-$(basename $script)}

    mkdir -p /root/firstrun.d
    cp $script /root/firstrun.d/${level}_$name
}

################################## adjust system timezone ############################

ln -sf ../usr/share/zoneinfo/$TIMEZONE /etc/localtime

################################## run other scripts in turn ##############

function enumerate_tasks() {
    for script in $INSTDIR/common.d/*; do
        case $(basename $script) in
            [0-9][0-9]_*)
                echo $(basename $script):$script
                ;;
        esac
    done

    for tsk in $(cat $INSTDIR/flavours/$FLAVOUR.tasks | sed 's/#.*$//g'); do
        if [[ -f $INSTDIR/tasks.d/$tsk ]]; then
            echo $tsk:$INSTDIR/tasks.d/$tsk
        else
            # fail to find task
            echo "$INSTDIR/flavours/$FLAVOUR.tasks: invalid task '$tsk'" >&2
            return 1
        fi
    done
    return 0
}


for script in $(enumerate_tasks | sort -k1 -t':' | cut -f2 -d':'); do
    echo "--------------------- start script $script ---------------------"
    . $script
    echo "--------------------- end of script $script ---------------------"
done

############################### cleanup ###################################

cd /
apt-get clean # clean apt caches
rm -rf /var/lib/apt/lists/*
rm -rf $INSTDIR # yes, I can auto-terminate myself !

# cleanup /tmp without removing the dir
for x in $(find /tmp -mindepth 1); do
    rm -rf $x || true
done

echo "------------------------ $(basename $0) finished -----------------------"

wait_for_net.sh

wait_for_net.sh
#!/bin/bash

### this script is used as entrypoint of the docker container to wait for network to be up ###

IFACE="veth0 eth0"

function wait_net() {
    for i in $IFACE; do
        [[ "$(cat /sys/class/net/$i/operstate 2>/dev/null)" == "up" ]] && return 1
        ip link set mtu 1300 dev $IFACE
    done
    return 0
}

while wait_net; do
    sleep 1
done

[[ $# > 0 ]] && exec "$@"
exec /bin/bash -l

Members

Platinum Members

Denso
Mazuda
Panasonic
Renesas
suzuki
TOYOTA

Advisory Board

HONDA
NTTDATA
DENSOTEN
Mercedes-Benz

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LinuxをMacにインストールしてみた

今回はターミナルにzshとtmuxをインストールしました。
かなり難しかったですが、なんとか達成できました。
またLinuxのコマンドについても学習しました。
たくさんの種類のコマンドがあり、大変便利だと感じました。
ターミナルからファイルやフォルダを作ることもできます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

catした結果をダブルクォーテーションで囲んで変数に入れる

コマンド

test=\"`cat test.json`\"
echo $test

説明

・バックスラッシュ「`」で囲まれた文字列はコマンドとして実行される。
・\"で「"」を入れられる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Arduinoで非同期処理(Linuxからの処理依頼を非同期にさばく)

別記事で [16x8 LEDマトリクスの表示を矯正する] を書きました。
その LED Matrix を使って、Linux からメッセージ表示 request を送れば、メッセージをスクロール表示させて response を返す Arduino、を作っていきます。
この記事のポイントは「Arduino での request 受信とメッセージ表示を非同期(厳密には疑似非同期)で行う」です。

使うもの。

  • Arduino NANO (NANO Every)
  • Adafruit LED Backpack Library
  • ArduinoJson
  • Ubuntu
  • pySerial3

Linux <-> arduino の USBシリアル接続確認

Arduino NANO を Linux 接続した時の Kernel Message
usb 1-12: new full-speed USB device number 3 using xhci_hcd
usb 1-12: New USB device found, idVendor=0403, idProduct=6001
usb 1-12: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 1-12: Product: FT232R USB UART
usb 1-12: Manufacturer: FTDI
usb 1-12: SerialNumber: ALxxxxxx
usbcore: registered new interface driver usbserial_generic
usbserial: USB Serial support registered for generic
usbcore: registered new interface driver ftdi_sio
usbserial: USB Serial support registered for FTDI USB Serial Device
ftdi_sio 1-12:1.0: FTDI USB Serial Device converter detected
usb 1-12: Detected FT232RL
usb 1-12: FTDI USB Serial Device converter now attached to ttyUSB0


Arduino NANO Every を Linux 接続した時の Kernel Message
usb 1-6: new full-speed USB device number 4 using xhci_hcd
usb 1-6: New USB device found, idVendor=2341, idProduct=0058
usb 1-6: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 1-6: Product: Arduino Nano Every
usb 1-6: Manufacturer: Arduino LLC
usb 1-6: SerialNumber: FAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
cdc_acm 1-6:1.0: ttyACM0: USB ACM device
usbcore: registered new interface driver cdc_acm
cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters

serial_test.ino
#include <Wire.h>

void setup()
{
  Wire.begin();
  Serial.begin(115200);
  Serial.println("serial test");
}

void loop()
{
  String str;  
  if(Serial.available()>0) {
    str = Serial.readString();
    Serial.println("[" + str + "]");
  }
}
screen で動作確認
$ screen /dev/ttyUSB0 115200
serial test
hogehoge
[hogehoge]

テキストをスクロール表示してみる

Linux から arduino に request を渡すと、内容の実行とともに response を返してくれるようにします。
とりあえず手始めに以下の機能を実装。データは JSON で受け渡します。

機能 request response
テキストのスクロール表示 {"textscr":"hello world !"} {"status":200, "msg":"..."}
Arduino のヘルスチェック {"status":{}} {"status":200, "msg":"..."}

以下 Arduino 主要コード抜粋

test.ino
int sendResponse(int status, String msg) {
  StaticJsonDocument<100> response;

  response["status"] = status;
  response["msg"] = msg;

  serializeJson(response, Serial);
  Serial.println();
}

int ExecCmd(String cmd, JsonVariant value) {
  if(cmd == "status") {
    sendResponse(100, "alive");

  } else if(cmd == "textscr") {
    JsonObject obj2 = value.as<JsonObject>();

    String msg = obj2["msg"];
    int size = obj2["size"];

    if(size < 1 || size > 2) {
      size = 1;
    }
    matrix.setTextSize(size);
    matrix.setTextWrap(false);
    matrix.setTextColor(LED_ON);

    int l = msg.length();
    for(int16_t x = 7; x >= -6*l; x--) {
      matrix.clear();
      matrix.setCursor(x,0);
      matrix.print(msg);
      matrix.writeDisplay();
      delay(100);
    }

    sendResponse(200, "msg:" + msg + ",size:" + (String)size);

  } else { // "status" : 404
    sendResponse(404, "command not found:" + cmd);
  }
}

StaticJsonDocument<200> request;

void loop() {
  if(Serial.available() > 0) {
    DeserializationError error = deserializeJson(request, Serial);
    if (error) {
      sendResponse(500, error.c_str());
      return;
    }

    JsonObject obj = request.as<JsonObject>();
    for (JsonPair p : obj) {
      ExecCmd((String)p.key().c_str(), p.value());
    }
  }
}

Arduino コード全体はここ
test.ino
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_LEDBackpack.h>
#include <ArduinoJson.h>

#ifndef _swap_int16_t
#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; }
#endif

class aitendo_KLED1608K33D_8x16matrix : public Adafruit_LEDBackpack, public Adafruit_GFX {
 public:
  aitendo_KLED1608K33D_8x16matrix(void);

  void drawPixel(int16_t x, int16_t y, uint16_t color);

 private:
};

aitendo_KLED1608K33D_8x16matrix::aitendo_KLED1608K33D_8x16matrix(void) : Adafruit_GFX(16, 8) {
}

void aitendo_KLED1608K33D_8x16matrix::drawPixel(int16_t x, int16_t y, uint16_t color) {
  if ((y < 0) || (x < 0)) return;
  if ((getRotation() % 2 == 0) && ((x >= 16) || (y >= 8))) return;
  if ((getRotation() % 2 == 1) && ((y >= 16) || (x >= 8))) return;

 // check rotation, move pixel around if necessary
  switch (getRotation()) {
  case 0:
    if (x >= 8) {
      x -= 8;
      y += 8; 
    }
    break;
  case 1:
    y = 16 - y - 1;
    if(y >= 8) {
      y -= 8;
      x += 8;
    }
    _swap_int16_t(x, y);
    break;
  case 2:
    x = 16 - x - 1;
    y = 8 - y - 1;
    if (x >= 8) {
      x -= 8;
      y += 8; 
    }
    break;
  case 3:
    x = 8 - x - 1;
    if(y >= 8) {
      y -= 8;
      x += 8;
    }
    _swap_int16_t(x, y);
    break;
  }

  if (color) {
    displaybuffer[x] |= 1 << y;
  } else {
    displaybuffer[x] &= ~(1 << y);
  }
}

aitendo_KLED1608K33D_8x16matrix matrix = aitendo_KLED1608K33D_8x16matrix();

void setup() {
  Serial.begin(115200);
  Serial.println("16x8 LED Matrix");

  matrix.begin(0x70);
  matrix.setBrightness(5);
  matrix.setRotation(0);
  matrix.clear();
  matrix.writeDisplay();
}

int sendResponse(int status, String msg) {
  StaticJsonDocument<100> response;

  response["status"] = status;
  response["msg"] = msg;

  serializeJson(response, Serial);
  Serial.println();
}

int ExecCmd(String cmd, JsonVariant value) {
  if(cmd == "status") {
    sendResponse(100, "alive");

  } else if(cmd == "textscr") {
    JsonObject obj2 = value.as<JsonObject>();

    String msg = obj2["msg"];
    int size = obj2["size"];

    if(size < 1 || size > 2) {
      size = 1;
    }
    matrix.setTextSize(size);
    matrix.setTextWrap(false);
    matrix.setTextColor(LED_ON);

    int l = msg.length();
    for(int16_t x = 7; x >= -6*l; x--) {
      matrix.clear();
      matrix.setCursor(x,0);
      matrix.print(msg);
      matrix.writeDisplay();
      delay(100);
    }

    sendResponse(200, "msg:" + msg + ",size:" + (String)size);

  } else { // "status" : 404
    sendResponse(404, "command not found:" + cmd);
  }
}

StaticJsonDocument<200> request;

void loop() {
  if(Serial.available() > 0) {
    DeserializationError error = deserializeJson(request, Serial);
    if (error) {
      sendResponse(500, error.c_str());
      return;
    }

    JsonObject obj = request.as<JsonObject>();
    for (JsonPair p : obj) {
      ExecCmd((String)p.key().c_str(), p.value());
    }
  }
}

以下 Linux 側コード
最初に Linux箱と Arduino の USB 接続確認までやっといてナンですが、ここではテスト環境 Windows Linux Subsystem の Ubuntu を使っています。

test.py
import serial
import time
import json

s = serial.Serial()
s.port = "/dev/ttyS4" # COM4 の arduino に接続
s.baudrate = 115200
s.timeout = 1
s.dtr = False   # serial 接続時に arduino にリセットがかかるのを抑止
s.open()
time.sleep(1)   # 気持ち待つ

s.reset_input_buffer()  # シリアルポートの受信バッファ掃除

def request(data):
    print("request:", data)
    s.write(json.dumps(data).encode())  # encode() でバイナリ列にする必要あり
    while True: # response が返ってくるのを待つ
        msg = s.readline().decode()
        if(len(msg) > 0):
            print("response:", msg)
            break

request({"textscr" : {"msg":"Hello World !!!"}})
request({"status" : {}})

実行結果
led.gif
"textscr" request の response は {"status":200,"msg":Hello World !!!"} と出るのが正しいのですが、この時は先頭の文字をこぼしたようです。エラーハンドリングも考えるか。。


テキストスクロール表示中の処理ブロッキングをなんとかする

実行結果見るとわかるのですが、textscr の表示が完了するまで response が返ってこないし、待ってる間は別のコマンドも受け付けてくれません。ちょっと扱いづらい。
Arduino 側のテキストスクロールは、文字表示しては 100ms 待って1ドット横にずらすというのを for ループで回しているので、テキストを 1ドットずらすごとに loop() に処理を戻してスクロール中でも次のコマンドを受け取れるようにします。加えて、スクロール表示中に新たな textscr が送られてきたら既存の表示実行は破棄して上書きするようにします。

Arduino 修正版コード全体はここ
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_LEDBackpack.h>
#include <ArduinoJson.h>

#ifndef _swap_int16_t
#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; }
#endif

class aitendo_KLED1608K33D_8x16matrix : public Adafruit_LEDBackpack, public Adafruit_GFX {
 public:
  aitendo_KLED1608K33D_8x16matrix(void);

  void drawPixel(int16_t x, int16_t y, uint16_t color);

 private:
};

aitendo_KLED1608K33D_8x16matrix::aitendo_KLED1608K33D_8x16matrix(void) : Adafruit_GFX(16, 8) {
}

void aitendo_KLED1608K33D_8x16matrix::drawPixel(int16_t x, int16_t y, uint16_t color) {
  if ((y < 0) || (x < 0)) return;
  if ((getRotation() % 2 == 0) && ((x >= 16) || (y >= 8))) return;
  if ((getRotation() % 2 == 1) && ((y >= 16) || (x >= 8))) return;

 // check rotation, move pixel around if necessary
  switch (getRotation()) {
  case 0:
    if (x >= 8) {
      x -= 8;
      y += 8; 
    }
    break;
  case 1:
    y = 16 - y - 1;
    if(y >= 8) {
      y -= 8;
      x += 8;
    }
    _swap_int16_t(x, y);
    break;
  case 2:
    x = 16 - x - 1;
    y = 8 - y - 1;
    if (x >= 8) {
      x -= 8;
      y += 8; 
    }
    break;
  case 3:
    x = 8 - x - 1;
    if(y >= 8) {
      y -= 8;
      x += 8;
    }
    _swap_int16_t(x, y);
    break;
  }

  if (color) {
    displaybuffer[x] |= 1 << y;
  } else {
    displaybuffer[x] &= ~(1 << y);
  }
}


aitendo_KLED1608K33D_8x16matrix matrix = aitendo_KLED1608K33D_8x16matrix();

#define DR_UNRELATED 0
#define DR_STOP 1
#define DR_OVERRIDE 2
#define DR_NEW 10
#define DR_CONTINUE 11

typedef struct {
  const char *cmd;
  int (*drawfunc)(int, JsonVariant);
} Cmds;
Cmds *drawing;

void setup() {
  drawing = NULL;

  Serial.begin(115200);
  Serial.println("16x8 LED Matrix");

  matrix.begin(0x70);
  matrix.setBrightness(5);
  matrix.setRotation(0);
  matrix.clear();
  matrix.writeDisplay();
}

int sendResponse(int status, String msg) {
  StaticJsonDocument<100> response;

  response["status"] = status;
  response["msg"] = msg;

  serializeJson(response, Serial);
  Serial.println();
}

int cmdStatus(int stat, JsonVariant value) {
  sendResponse((drawing ? 102 : 100), (drawing ? "drawing" : "free time"));
  return(DR_UNRELATED);
}

int cmdTextscr(int stat, JsonVariant value) {
  static String sMsg;
  static int16_t l, x;

  if(stat == DR_NEW) {
    JsonObject obj = value.as<JsonObject>();

    String msg = obj["msg"];

    sMsg = msg;
    l = msg.length();
    x = 7;

    matrix.setTextSize(1);
    matrix.setTextWrap(false);
    matrix.setTextColor(LED_ON);
  }

  if(x >= -6*l) {
    matrix.clear();
    matrix.setCursor(x,0);
    matrix.print(sMsg);
    matrix.writeDisplay();
    delay(100);
    x--;
  } else {
    //sendResponse(200, "finish textscr msg:" + msg);
    return(DR_STOP);
  }

  if(stat == DR_NEW) {
    sendResponse(200, "textscr msg:" + sMsg);
    return(DR_OVERRIDE);
  }
  return(DR_UNRELATED);
}

Cmds cmds[] = {
  {"status", cmdStatus},
  {"textscr", cmdTextscr},
  {"", NULL},
};

StaticJsonDocument<200> request;
JsonVariant JVNULL = JsonVariant();

void loop() {
  if(Serial.available() > 0) {
    DeserializationError error = deserializeJson(request, Serial);
    if (error) {
      sendResponse(500, error.c_str());
      return;
    }

    JsonObject obj = request.as<JsonObject>();
    for (JsonPair p : obj) {
      String cmd = (String)p.key().c_str();
      int i;
      for(i = 0; cmds[i].cmd != ""; i++) {
        if((String)cmds[i].cmd == cmd) {
          int r = (*cmds[i].drawfunc)(DR_NEW, p.value());
          switch(r) {
            case DR_OVERRIDE:
              drawing = &cmds[i];
              break;
            case DR_STOP:
              drawing = NULL;
              break;
          }
          break;
        }
      }
      if(cmds[i].cmd == "") {
        sendResponse(404, "command not found:" + cmd);
      }
    }

  } else {
    if(drawing) {
      int r = drawing->drawfunc(DR_CONTINUE, JVNULL);
      switch(r) {
        case DR_STOP:
          drawing = NULL;
          break;
      }
    }
  }
}

以下抜粋。

test2.ino
#define DR_UNRELATED 0  // cmdxxxx の処理が、実行中の他の request を上書きしない場合に返す
#define DR_STOP 1       // cmdxxxx の処理が完了したときに返す
#define DR_OVERRIDE 2   // cmdxxxx の処理が、実行中の他の request を上書きする場合に返す
#define DR_NEW 10       // 新たな request が来たときに cmdxxxx に渡す
#define DR_CONTINUE 11  // 引き続きの処理で cmdxxxx を呼ぶ時に渡す

int cmdStatus(int stat, JsonVariant value) {
  ...
}

int cmdTextscr(int stat, JsonVariant value) {
  ...
}

typedef struct {
  const char *cmd;
  int (*drawfunc)(int, JsonVariant);
} Cmds;

Cmds *drawing;

Cmds cmds[] = {
  {"status", cmdStatus},
  {"textscr", cmdTextscr},
  {"", NULL},
};

void loop() {
  if(Serial.available() > 0) {
    ...
    for (JsonPair p : obj) {
      String cmd = (String)p.key().c_str(); // 受け取った request 文字列を cmd に格納
      int i;
      for(i = 0; cmds[i].cmd != ""; i++) { // cmd に一致する cmds[] メンバーを探して呼ぶループ
        if((String)cmds[i].cmd == cmd) {
          int r = (*cmds[i].drawfunc)(DR_NEW, p.value());
          switch(r) {
            case DR_OVERRIDE:
              drawing = &cmds[i]; // 今よびだした関数は以降も処理を継続するので記憶しておく
              break;
            case DR_STOP:
              drawing = NULL; // 今よびだした関数はもう完了したので継続処理する関数はなし
              break;
          }
          break;
        }
      }
      ...
    }

  } else {
    if(drawing) { // 実行中の request 関数があれば、その関数を呼ぶ
      int r = drawing->drawfunc(DR_CONTINUE, JVNULL);
      ...
    }
  }
}
  1. request コマンド(今は "textscr" "status" の二つだけ)毎にLED表示関数 cmdxxxx を用意して Cmds 構造体のリストである cmds[] にコマンド名と一緒につっこんでおく。
  2. 例えば request "textscr" を Linux から送ると loop() 内から cmdTextscr が stat = DR_NEW で呼ばれてスクロール表示を始める。
  3. cmdTextscr はテキスト表示して 100ms 待って、まだスクロールの終わりまで行ってなかったら DR_OVERRIDE か DR_UNRELATED を返す。スクロール表示最後まで完了してたら DR_STOP を返す。
  4. 返事をもらった loop() 側は、DR_STOP だったら drawing = NULL, DR_OVERRIDE だったら実行中ということで drawing = &cmds[1] を覚えておく。
  5. loop() は上記を for ループで繰り返して、drawing != NULL だったら実行中の処理を呼ぶ、また新しい request が来れば実行するを繰り返す。

これで Linux 側では request を送ったあと待たされることもないし、任意のタイミングで request が送れるようになった。

test2.py
import serial
import time
import datetime
import json

s = serial.Serial()
s.port = "/dev/ttyS4"
s.baudrate = 115200
s.timeout = 1
s.dtr = False   # serial 接続時に arduino にリセットがかかるのを抑止
s.open()
time.sleep(1)   # 気持ち待つ

s.reset_input_buffer()  # シリアルポートの受信バッファ掃除

def request(data):
    print(datetime.datetime.now().strftime('%H:%M:%S'), "request:", data)
    s.write(json.dumps(data).encode())  # encode() でバイナリ列にする必要あり
    while True: # response が返ってくるのを待つ
        msg = s.readline().decode()
        if(len(msg) > 0):
            print(datetime.datetime.now().strftime('%H:%M:%S' ), "response:", msg)
            break

request({"textscr" : {"msg":"Hello World !!!"}})
time.sleep(3)
request({"status" : {}})
time.sleep(1)
request({"textscr" : {"msg":"\\(^_^)/"}})
time.sleep(6)
request({"status" : {}})
test2.py実行結果
ubuntu:~$ python3 test.py
00:25:39 request: {'textscr': {'msg': 'Hello World !!!'}}
00:25:39 response: {"status":200,"msg":"textscr msg:Hello World !!!"}

00:25:42 request: {'status': {}}
00:25:42 response: {"status":102,"msg":"drawing"}

00:25:43 request: {'textscr': {'msg': '\\(^_^)/'}}
00:25:43 response: {"status":200,"msg":"textscr msg:\\(^_^)/"}

00:25:49 request: {'status': {}}
00:25:49 response: {"status":100,"msg":"free time"}

わかりやすいように request, response の前に時刻を付加してます。
テキストスクロールを始めたらすぐに response 返ってくるし、まだスクロール中に別の textscr request を送れば新しい request を実行します。ばっちり。
おわり。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

よく使うLinuxコマンド

Linuxでよく使うコマンドやショートカットキーをまとめました。

対象読者は、ターミナルに苦手意識のあるエンジニアです。
初心者向けに書きましたが、そこそこテクい書き方もでてきます。
普段使いしている方にも案外発見があるかも? しれません。

この記事のスタンス

  • わかりやすさ >> 正確さ
  • よく使うものを紹介 >> 網羅的に紹介
  • デフォルトで使える >> 別途インストールが必要
  • こんなときに使うよ、みたいな主観的なコメントを大事にしたい
  • ディレクトリって何? 標準出力って何? とかまでは説明しない

一覧

大項目 紹介するもの
はじめに〜コマンド入力をラクにする Tab補完, bashショートカット
ディレクトリの確認 pwd, ls, tree
階層移動とファイル操作 cd, mkdir, touch, mv, cp, rm, tar
テキスト処理(フィルタコマンド) cat, wc, head, tail, sort, uniq, grep, sed, awk, xargs, less, >, >>(リダイレクト)
インストールまわり apt, yum, sudo, su, echo, which, wheris, source, ., chmod, chown, systemctl
OSまわり date, df, du, free, top, ps, kill, pkill, pgrep, netstat
その他 find, history, diff, jobs, fg, bg, &(バックグラウンド実行), &&, \$()(コマンド置換), <()(プロセス置換), \$?, for
書かないことにしたやつ vi, make, curl, rsync, ssh-keygen, npm, git
おまけ nice, sl

はじめに〜コマンド入力をラクにする

コマンド全てを手入力するのは、しんどいです。
Tab補完やbashのショートカットなどを使ってラクをしましょう。
-> https://www.atmarkit.co.jp/ait/articles/1603/09/news019.html

最低限、コマンド名やファイル名はTabキーを打って補完したほうがよいと思います。
bashのショートカットはいろいろありますが、個人的には

  • Ctrl+A, Ctrl+E(行頭、行末へカーソル移動)
  • Ctrl+U, Ctrl+K(行頭まで削除、行末まで削除) # パスワードを打ち間違えたときに便利
  • Ctrl+W(1単語分コマンドを削除)
  • Ctrl+L(ターミナルの表示をクリア)
  • ↑, ↓, Ctrl+R(コマンド履歴の参照)
  • Ctrl+C(実行中のコマンドを中止)

あたりの使用頻度が高いです。
これらはreadlineというライブラリが提供するショートカットです。 # Ctrl+Cは別かも
bashに限らず多くのコマンドラインツールで使えるため、覚えておくと何かと便利です。
(例えばpythonやmysqlでも使えます)


ディレクトリの確認

コマンド名 何ができる? コマンド名は何に由来している?
pwd 今いるディレクトリの絶対パスを表示 print working directory
ls ファイルやディレクトリを表示 list
tree ディレクトリ構造を表示 directory tree

pwd

# pwd: 今いるフォルダの絶対パスを表示
arene@~/qiita $ pwd
/home/arene/qiita


ls

# ls: 現在のフォルダにあるファイルやディレクトリを表示
$ ls
dir1  file1.txt  file2.txt  file3.txt

# ls -al:
#  * -a: 隠しファイルも表示(由来: all)
#  * -l: 詳細な情報を表示(由来: list?)
#  * とにかく全部みたいときに使う
#  * 大きいファイルがある場合はls -alhにすると見やすい
#    -h: M(メガ)、G(ギガ)などを付けてサイズを見やすくする(由来:human readable)
#  * パーミッション(左端に出るrとかwとか)や所有者(arene areneのところ)はインストールでエラーが起きた時によく見る
#    -> 変えたい場合はそれぞれchmod, chownを使う
#  * llでls -alやls -lと同じ動作になる環境もよく見かけます (キーワードはalias(エイリアス)。下記リンク参照)
$ ls -al
total 0
drwxr-xr-x 1 arene arene 4096 Nov 10 18:07 .
drwxrwxrwx 1 arene arene 4096 Nov 10 18:04 ..
-rw-r--r-- 1 arene arene    0 Nov 10 18:07 .hidden_file1.txt
-rw-r--r-- 1 arene arene    0 Nov 10 18:14 dir1
-rw-r--r-- 1 arene arene    4 Nov 10 18:04 file1.txt
-rw-r--r-- 1 arene arene    0 Nov 10 18:02 file2.txt
-rw-r--r-- 1 arene arene    0 Nov 10 18:02 file3.txt

# ls -ltr: 新しいファイルが一番下に来るように表示
#  * -t: タイムスタンプ順で表示(由来: time)
#  * -r: 逆順で表示(由来: reverse)
#  * 最新のログファイルを探す目的でよく使う
#  * 最新が一番下に来るので、ファイル数が多くても見切れない
#  * 逆に、一番古いファイルを見たい場合はls -lt
$ ls -ltr
total 0
-rw-r--r-- 1 arene arene  123 Oct 10 02:30 20191010.log
-rw-r--r-- 1 arene arene  123 Oct 11 02:30 20191011.log
-rw-r--r-- 1 arene arene  123 Oct 12 02:30 20191012.log
-rw-r--r-- 1 arene arene  123 Oct 13 02:30 20191013.log
-rw-r--r-- 1 arene arene  123 Oct 14 02:30 20191014.log
-rw-r--r-- 1 arene arene  123 Oct 15 02:30 20191015.log
-rw-r--r-- 1 arene arene  123 Oct 16 02:30 20191016.log
-rw-r--r-- 1 arene arene  123 Oct 17 02:30 20191017.log
-rw-r--r-- 1 arene arene  123 Oct 18 02:30 20191018.log
-rw-r--r-- 1 arene arene  123 Oct 19 02:30 20191019.log  ←最新ファイルが一番下に来る
$

余談: 世の中のエンジニアのalias設定


tree ※別途インストールが必要

# tree: ディレクトリ構造を表示
#  * ls -Rでも同様の情報が分かるけど、見づらい。
#  * sudo apt install treeやyum install treeでインストールできます
#  * 今後の説明でよく使うため紹介
$ tree
.
|-- dir1
|   |-- dir11
|   |   |-- file111.txt
|   |   `-- file112.txt
|   |-- file11.txt
|   |-- file12.txt
|   `-- file13.txt
|-- file1.txt
|-- file2.txt
`-- file3.txt


階層移動とファイル操作

コマンド名 何ができる? コマンド名は何に由来している?
cd 階層移動(カレントディレクトリの変更) change directory
mkdir ディレクトリの作成 make directory
touch ファイルの作成、タイムスタンプ更新 ??
mv ファイルやディレクトリの移動 move
cp ファイルやディレクトリの移動 copy
rm ファイルの削除 remove
tar ファイルの圧縮、展開(tar形式) tape archives(←はじめて知った)


cd

# cd path: pathへ移動
arene@~/qiita $ ls
dir1  file1.txt  file2.txt  file3.txt
arene@~/qiita $ cd dir1/
arene@~/qiita/dir1 $ pwd
/home/arene/qiita/dir1

# cd: ログインユーザのホームディレクトリに移動
arene@~/qiita/dir1 $ cd
arene@~ $ pwd
/home/arene

# cd -: 直前にいたディレクトリへ移動
#  * 階層の離れた2つのディレクトリを行き来するときに便利
#  * pushd, popdコマンドでも似たようなことができるけど、こっちの方が好き
arene@~/qiita/dir1 $ pwd
/home/arene/qiita/dir1
arene@~/qiita/dir1 $ cd
arene@~ $ pwd
/home/arene

arene@~ $ cd -
arene@~/qiita/dir1 $ pwd
/home/arene/qiita/dir1

arene@~/qiita/dir1 $ cd -
arene@~ $ pwd
/home/arene

# cd ~/path: ログインユーザのホームディレクトリ以下のpathに移動
#  * ~はログインユーザのホームディレクトリに読み替えられる(=チルダ展開)
#  * ~xxxだとxxxユーザのホームディレクトリに読み替えられる
arene@~/qiita/dir1 $ cd ~/bin/
arene@~/bin $ pwd
/home/arene/bin


mkdir

# mkdir directory_name: ディレクトリを作成(1階層のみ)
# mkdir -p path/to/directory: 深い階層のディレクトリを一気に作成
$ ls  # 最初は何もない状態

$ mkdir dir1  # ディレクトリを作成
$ ls
dir1

$ mkdir aaa/bbb/ccc # 深い階層を一気に作るには-pオプションが必要
mkdir: cannot create directory ‘aaa/bbb/ccc’: No such file or directory
$ ls
dir1
$ mkdir -p aaa/bbb/ccc
$ tree
.
|-- aaa
|   `-- bbb
|       `-- ccc
`-- dir1


touch

# touch file_name: ファイルを新規作成 or タイムスタイプを現在時刻に更新
#  * 本来はファイルのタイムスタンプを更新するコマンドなんだけど、
#    指定したファイルが存在しない場合は新規作成するため、専らファイル作成コマンドと化している印象
$ touch file1  # 新規作成
$ ls -l
-rw-r--r-- 1 arene arene 0 Nov 10 10:10 file1
$ touch file1  # 5分後に再実行 -> タイムスタンプが更新される
$ ls -l
-rw-r--r-- 1 arene arene 0 Nov 10 10:15 file1

# touch --date="YYYYMMDD hh:mm:ss" file_name: ファイルのタイムスタンプを任意の時刻に更新
#  * 時刻がらみの動作確認をする目的でまれによく使う
#  * 関連コマンド
#    date -s "YYYYMMDD hh:mm:ss": OS時刻変更 (-sはsetの意味)
#    (--dateオプションの由来と思われる)
$ touch --date "20101010 10:10:10" file1
$ ls -l
total 0
-rw-r--r-- 1 arene arene 0 Oct 10  2010 file1

# 応用:
# ブレース展開と組み合わせると大量の試験ファイルを簡単に作れます
# ブレース展開(連番ver): {num1..num2}がnum1~num2までの連番に展開されるbashの機能
#                       (列挙verは次項(mv)を参照)
$ touch file{1..3}.txt # -> touch file1.txt file2.txt file3.txt と展開される
$ ls
file1  file2.txt  file3.txt


mv

# mv source/path destination/path: ファイルやディレクトリの移動
# mv filename_before filename_after: リネーム
#  * OS管理上リネームとファイル移動は大体同じ
#  * 確認がうるさいときは-fオプションをつける(f: force / 誤操作注意!)
$ tree
.
|-- dir1
|-- dir2
`-- file1

$ mv file1 dir1  # 移動
$ tree
.
|-- dir1
|   `-- file1
`-- dir2

$ mv dir1/file1 dir1/file1.txt  # リネーム
$ tree
.
|-- dir1
|   `-- file1.txt
`-- dir2

# 応用:
# ディレクトリ構成によっては、ブレース展開と組み合わせると簡潔に記述できます
# ブレース展開(列挙ver): cmd {aaa,bbb,ccc}がcmd aaa bbb cccに展開されるbashの機能
#                        ※スペースを入れて{aaa, bbb, ccc}としないよう注意
$ tree
.
|-- dir1
|   `-- file1.txt
`-- dir2

$ mv dir{1,2}/file1.txt # mv dir1/file1.txt dir2/file1.txtに展開される
$ tree
.
|-- dir1
`-- dir2
    `-- file1.txt

# 応用2:
# mvした後にcd !$とするとファイルの移動先へスムーズに移動できます
#  * bashでは!$を使うと、直前に実行したコマンドの末尾の引数に展開される
#  * !$は!-1$のエイリアス。!-2$だと2個前に実行したコマンドの末尾の引数に展開される
#  * !はコマンド履歴の展開を表す。$が末尾引数なのは正規表現と同じイメージ
#  * !系のコマンドは黒魔術めいててあまり好きになれないけど、
#    mv -> cd !$だけはめっちゃ使うし便利なので覚えました。(cpも同様)
#  * mv -> ls -> cd !-2$ もよくやります
arene@~/qiita $ ls
dir1  file1
arene@~/qiita $ mv file1 dir1/a/b/c/d/e/f/g/
arene@~/qiita $ cd !$   # !$は直前のコマンドの末尾の引数=a/b/c/d/e/f/g/
cd dir1/a/b/c/d/e/f/g/  # !系のコマンドを使うと展開結果が標準出力に出てくる
arene@~/qiita/dir1/a/b/c/d/e/f/g $ ls
file1

履歴展開の詳しい情報: https://mseeeen.msen.jp/bash-history-expansion/


cp

# cp -r source/path destination/path: ファイルやディレクトリのコピー
#  * -r: ディレクトリ以下を再帰的にコピー(由来: recursive)
#  * -f: 確認無しで強制コピー(由来: force)  <- 誤操作注意!
#  * -p: コピー前後でパーミッションを保持(由来: permission)
#  * 私は常にrを付ける派(別にあっても悪さしないし、ディレクトリかファイルかでオプション使い分けるのがめんどくさい)
#  * fは誤操作を水際で止めてくれるかもなので、基本付けない派
#  * パーミッションが大事な場面ではpの有無に気を配る(普段は手癖で付けちゃう派)
#  * 類似コマンドscpもよく使います。
#    ネットワーク越しにファイルやディレクトリをコピーできます(書式は代替同じ)
$ tree
.
|-- dir1
|   `-- file1
`-- dir2

$ cp dir1/file1 dir2  # ファイルを別フォルダにコピー
$ tree
.
|-- dir1
|   `-- file1
`-- dir2
    `-- file1

$ cp dir1/file1 dir2/file2  # リネームしつつファイルを別フォルダにコピー
$ tree
.
|-- dir1
|   `-- file1
`-- dir2
    |-- file1
    `-- file2

# 応用:
# ブレース展開と組み合わせるとバックアップファイル作成などを簡潔に記述できます
# ブレース展開(列挙ver): cmd {aaa,bbb,ccc}がcmd aaa bbb cccに展開されるbashの機能
$ ls
important_file
$ cp important_file{,.bak}  # cp important_file important_file.bakに展開される
$ ls
important_file  important_file.bak


rm

# rm -f file_name: ファイルを削除
# rm -rf directory_name: ディレクトリを削除
#  * -f: 確認無しで強制コピー(由来: force)
#  * -r: ディレクトリ以下を再帰的に削除(由来: recursive)
#  * Windowsと違って消したら戻せないので、よくよく気を付けよう
#  * うっかり「rm -rf /」を実行するとOSを含めシステムが全削除されます
#    慌ててCtrl+Cで止めたが、基本的なコマンドの実行ファイルが削除されていてまともに作業できない!
#    ...みたいなことになります。(昔、隣の席の人がやらかしてた)
#  * シェルスクリプトで rm -rf /${DIR_PATH} と書いたが${DIR_PATH}が空文字で
#    「rm -rf /」となるのはありがちかも。
#    (こういう事故が起きないよう、シェルスクリプトではset -uをつけるのがベター(下記リンク参照))
$ ls  # 初期状態
dir1  dir2  dir3  file1.txt  file2.txt  file3.txt

$ rm -f file1.txt  # ファイル名を指定して削除
$ ls
dir1  dir2  dir3  file2.txt  file3.txt

$ rm -f *.txt  # ワイルドカード(*)を使ってtxtファイルを全削除
$ ls
dir1  dir2  dir3

$ rm -f dir1  # rオプションがないとディレクトリは削除できない
rm: cannot remove 'dir1': Is a directory

$ rm -rf dir1  # ディレクトリ名を指定して削除
$ ls
dir2  dir3

$ rm -rf dir*  # ワイルドカード(*)を使って一括削除
$ ls
$ 

余談: シェルスクリプトを書くときはset -euしておく


tar

# tar -czvf xxx.tgz file1 file2 dir1 : 圧縮(file1 file2 dir1をアーカイブした圧縮ファイルxxx.tgzを作成)
# tar -tzvf xxx.tgz: 圧縮ファイルに含まれるファイル名を表示(=展開のテスト)
# tar -xzvf xxx.tgz: 展開
#  * tarのオプションはハイパーややこしい
#    ...んだけど、普通に作業する分には上の3つで事足りると思う
#  * c(create), t(test), x(extract) + zvfと覚える
#  * なおアーカイブと圧縮は別の事象。
#    複数ファイルを一つにまとめるのがアーカイブ。ファイル容量を削減するのが圧縮。
$ ls  # 初期状態
dir1  dir2  file1.txt  file2.txt

$ tar czvf something.tgz dir* file*  # 圧縮
dir1/
dir2/
file1.txt
file2.txt
$ ls
dir1  dir2  file1.txt  file2.txt  something.tgz

$ rm -rf dir* file*  # 一旦元ファイルを削除
$ ls
something.tgz

$ tar tzvf something.tgz  # 中身だけ見る
drwxr-xr-x arene/arene       0 2019-11-12 00:31 dir1/
drwxr-xr-x arene/arene       0 2019-11-12 00:30 dir2/
-rw-r--r-- arene/arene       0 2019-11-12 01:00 file1.txt
-rw-r--r-- arene/arene       0 2019-11-12 01:00 file2.txt
$ ls
something.tgz

$ tar xzvf something.tgz  # 展開
dir1/
dir1/file1.txt
dir2/
file1.txt
file2.txt
$ ls
dir1  dir2  file1.txt  file2.txt  something.tgz

# 余談:
# tarは圧縮、展開できているにもかかわらず0以外の終了ステータスを返すことが結構あります。
# (ex. ファイルのタイムスタンプが未来時刻のとき)
# シェルスクリプトとかでtarの終了ステータスをとったり、set -eするときは要注意


テキスト処理(フィルタコマンド)

Linuxの醍醐味、テキスト処理。
コマンドの説明に移る前に、テキスト処理の概要を説明します。

  • bashにはパイプ(|)と呼ばれる構文がある
  • コマンドA | コマンドB | コマンドCと書いたら、次の動きとなる
    • コマンドAは処理結果(文字列A)を標準出力に出す
    • コマンドBは標準入力から文字列Aを受け取り、処理結果(文字列B)を標準出力に出す
    • コマンドCは標準入力から文字列Bを受け取り、処理結果(文字列C)を標準出力に出す
  • コマンドA、B、Cのような、標準入力から文字列をinputして 標準出力へ文字列をoutputするコマンドを「フィルタコマンド」という
  • フィルタコマンドを使って文字列を加工することを「テキスト処理」という
  • また、パイプ等を駆使してコマンド1行でいろんな処理をすることを俗に「ワンライナー」という

・・・というわけで、Let's テキスト処理!

コマンド名 何ができる? コマンド名は何に由来している?
cat ファイル内容を結合して出力 concatenate(結合)
wc 単語数、行数を数える word count
head 先頭からn行を出力 head(先頭)
tail 末尾からn行を出力 tail(末尾)
sort 行単位でソート sort
uniq 重複を排除 unique
grep テキスト検索 global regular expression print
sed 文字列置換 stream editor
awk unix伝統のプログラム言語 開発者3名の頭文字
xargs 標準入力をコマンドライン引数に変換 execute arguments?
less 標準入力をエディタっぽく表示 less is more(moreコマンドの上位互換)
>, >>(リダイレクト) 標準入力をファイルに書き出す

※less, >, >>はフィルタコマンドじゃない気がしますが、テキスト処理の終端でよく使うためここで紹介します

cat

# cat file1: file1の内容を標準出力に出力
# cat file1 file2: file1の内容を標準出力に出力 -> file2の内容を標準出力に出力
#  * 用途1: 数行程度のファイルを、read onlyで見る
#  * 用途2: 複数のログファイルをひとまとめにして確認
#  * フィルタ処理の起点に使うことが多い
$ cat .gitignore  # 単一ファイルをdump
.DS_Store
node_modules
/dist

$ ls
access.log  error1.log  error2.log
$ cat error*.log  # error1.logとerror2.logをまとめて確認
2019/09/14 22:40:33 [emerg] 9723#9723: invalid number of arguments in "root" directive in /etc/nginx/sites-enabled/default:45
2019/09/14 22:42:24 [notice] 9777#9777: signal process started
2019/09/14 22:49:23 [notice] 9975#9975: signal process started
2019/09/14 22:49:23 [error] 9975#9975: open() "/run/nginx.pid" failed (2: No such file or directory)
2019/09/14 22:56:00 [notice] 10309#10309: signal process started
2019/09/14 22:56:10 [notice] 10312#10312: signal process started
2019/09/14 22:56:10 [error] 10312#10312: open() "/run/nginx.pid" failed (2: No such file or directory)
2019/09/14 22:56:22 [notice] 10318#10318: signal process started
2019/09/14 22:56:22 [error] 10318#10318: open() "/run/nginx.pid" failed (2: No such file or directory)
2019/12/07 21:49:50 [notice] 1499#1499: signal process started
2019/12/07 21:49:50 [error] 1499#1499: open() "/run/nginx.pid" failed (2: No such file or directory)
2019/12/07 21:51:19 [emerg] 1777#1777: invalid number of arguments in "root" directive in /etc/nginx/sites-enabled/default:45


wc

# wc -l file1: file1の行数を数える(行数 + ファイル名が出る)
# cat file1 | wc -l: file1の行数を数える(行数だけ出る)
#  * -l: 行数を数える(由来: line)
#  * -w: 単語数を数える(由来: word)
#  * -c: バイト数を数える(由来: char? / 1文字1バイトだった時代にできたオプションゆえcなのだと思われる)
#  * 正直-lしか使ったことない(バイト数とかlsで事足りるし)
#  * 簡易的に正直-lしか使ったことない(バイト数ならlsで事足りるし)
#  * プログラムから使う場合ファイル名は邪魔なのでcat | wc -lを使うケースが多いと思う
$ ls
access.log  error1.log  error2.log
$ wc -l error1.log  # 行数カウント(1)
7 error1.log
$ wc -l error2.log  # 行数カウント(2)
5 error2.log
$ wc -l error*.log  # ワイルドカード指定で複数ファイルの行数をカウント
   7 error1.log
   5 error2.log
  12 total
$ cat error*.log | wc -l  # error1.logとerror2.logを結合したうえで行数をカウント
12

# 応用: 簡易的にステップ数を数える
$ ls
dist    src    node_modules    package.json    public    tests
$ find src/ -type f | xargs cat | wc -l  # src以下の全ファイルの行数の合計をカウント
271
# 解説
#  * find src/ -type f: src以下のファイルの一覧を出力
#  * cmd1 | cmd2: cmd1が標準出力に出した内容を、標準入力として受け取ってcmd2を実行
#  * cmd1 | xargs cmd2: cmd1が標準出力に出した内容を、「コマンドライン引数」として受け取ってcmd2を実行
#  * find | xargs cat: findで検索したsrc以下のファイル全部を結合して標準出力に出す
#  * find | xargs cat | wc -l: findで検索したsrc以下のファイル全部を結合したものの行数をカウント
#  * findやxargsの項もご参照ください


head

# head -n 3 file1: file1の先頭3行を出力
#  * 言うほど使わないけど、次に紹介するtailと対になるコマンドなので紹介
#  * 用途1: 重たいファイルの頭の方だけ確認
#  * 用途2: ファイルのヘッダ行だけ取得してプログラムから使う
$ cat file1.txt
1 aaa AAA
2 bbb BBB
3 ccc CCC
4 ddd DDD
5 eee EEE
6 fff FFF
7 ggg GGG
8 hhh HHH
9 iii III
10 jjj JJJ
11 kkk KKK
12 lll LLL
13 mmm MMM

$ head -n 3 file1.txt
1 aaa AAA
2 bbb BBB
3 ccc CCC


tail

# tail -n 3 file1: file1の末尾3行を出力
#  * 用途1: 重たいログファイルの最後の方だけ見る
#  * tailといば、次のtail -fの使い方がメイン
$ cat file1.txt
1 aaa AAA
2 bbb BBB
3 ccc CCC
4 ddd DDD
5 eee EEE
6 fff FFF
7 ggg GGG
8 hhh HHH
9 iii III
10 jjj JJJ
11 kkk KKK
12 lll LLL
13 mmm MMM

$ tail -n 3 file1.txt
11 kkk KKK
12 lll LLL
13 mmm MMM


# tail -f error.log: error.logを監視して、更新された内容を出力(由来: feed?)
#  * ログ確認で大活躍 (末尾出力なので最新のログが出てくる)
#  * tail -fしてファイルの更新を監視することを俗に"tailさせる"と言う(別々の現場で聞いたことがある)
#  * ログファイルをtailさせて網を張る (適宜grepで絞り込みもしておく)
#    -> 不具合を再現させる
#    -> 不具合発生時時に出たログをピンポイントで抽出する
#    という使い方。
$ tail -f error.log  # ログ監視
-> Ctrl+Cで止めるまで、error.logの更新を待ち続ける
-> error.logが更新されたら、更新内容をそのまま出力する

$ tail -f error.log | grep 500  # 500を含むログだけ監視
-> Ctrl+Cで止めるまで、error.logの更新を待ち続ける
-> error.logに500を含むログが更新されたら、出力する


sort, uniq

# sort file1: file1を行単位でソート
# uniq file1: file1の重複業を削除
# cat file1 | sort | uniq: file1をソートして、重複業を排除
#  * sortとuniqはワンセット的なところがあるのでまとめて紹介
#  * sort -> uniqの順番が大切(下記例を参照)
#  * 元のファイルは変わらない(フィルタコマンド共通の性質)
#
#  * sortは-rで逆順ソート、-Rでランダムソート、みたいに結構オプションが多彩
#  * ls -lの実行結果をファイルサイズ順でsortする、みたいに
#    特定の列にだけ着目してソートすることも可能(この場合だとls -lSでいける)
#  * けど、正直覚えてない
#  * sortに限らず、ややこしいことをするなら普通のプログラミング言語を使った方がいい
#    (手元のcsvファイルからちょっと重複行を排除したい、みたいなイージーケースで使うくらいでいいと思う)
$ cat not_sorted_and_not_unique.txt
1 aaa AAA
3 ccc CCC
2 bbb BBB
3 ccc CCC
2 bbb BBB
1 aaa AAA
3 ccc CCC

$ cat not_sorted_and_not_unique.txt | sort
1 aaa AAA
1 aaa AAA
2 bbb BBB
2 bbb BBB
3 ccc CCC
3 ccc CCC
3 ccc CCC

$ cat not_sorted_and_not_unique.txt | sort | uniq
1 aaa AAA
2 bbb BBB
3 ccc CCC

$ cat not_sorted_and_not_unique.txt | uniq | sort  # sort -> uniqを逆にすると、期待結果が得られない
1 aaa AAA
1 aaa AAA
2 bbb BBB
2 bbb BBB
3 ccc CCC
3 ccc CCC
3 ccc CCC

# 小ネタ: 乱数生成
$ echo {1..65535} | sed 's/ /\n/g' | sort -R | head -n 1
11828
# 解説
#  * echo {1..65535}: "1 2 3 4 5 (中略) 65535"を生成(キーワード: ブレース展開(上述))
#  * sed 's/ /\n/g': スペースを改行に置換
#  * sort -R:行単位でランダムソート
#  * head -n 1:先頭1行だけ表示
#  * 1秒ほどかかるし、実用性は皆無
#  * sort -Rを初めて知って、ちょっと嬉しくなって書いてみました
#  * ただ、こういう風にコマンドをつなげて、工夫次第でいろんなことができるのがフィルタコマンドの楽しさだと思います
#    (その魅力に取りつかれたのが、いわゆる"シェル芸人")

もっと知りたい方へ: sortコマンド、基本と応用とワナ


grep

# grep ERROR *.log: 拡張子がlogのファイルから、ERRORを含む行だけ抽出
# cat error.log | grep ERROR: error.logからERRORを含む行だけ抽出
# cat error.log | grep -2 ERROR: error.logからERRORを含む行とその前後2行を出力
# cat error.log | grep -e ERROR -e WARN: error.logからERRORまたはWARNを含む行を抽出
# cat error.log | grep ERROR | grep -v 400: error.logからERRORを含む行を抽出して、400を含む行を排除した結果を表示
#  * -e: 複数キーワードをAND条件で指定(由来: ?? たぶん違うけど、個人的にはフランス語のet(=and)だと解釈してる)
#  * -v: キーワードを含む行を排除(由来: verbose??)
#  * テキスト絞り込み、という高需要な用途ゆえワンライナーの中終盤で大活躍
#  * 正規表現も使えます
#  * 個人的にはcat | grep形式しか使わない(フィルタコマンドは全部脳死でcat | cmdする派)
$ cat file1.txt
1 aaa AAA
2 bbb BBB
3 ccc CCC
4 ddd DDD
5 eee EEE
6 fff FFF
7 ggg GGG
8 hhh HHH
9 iii III
10 jjj JJJ
11 kkk KKK
12 lll LLL
13 mmm MMM

$ cat file1.txt | grep -e CCC -e JJJ
3 ccc CCC
10 jjj JJJ

$ cat file1.txt | grep -2 -e CCC -e JJJ
1 aaa AAA
2 bbb BBB
3 ccc CCC
4 ddd DDD
5 eee EEE
--
8 hhh HHH
9 iii III
10 jjj JJJ
11 kkk KKK
12 lll LLL

$ cat file1.txt | grep -2 -e CCC -e JJJ | grep -v -e AAA -e BBB -e KKK -e LLL
3 ccc CCC
4 ddd DDD
5 eee EEE
--
8 hhh HHH
9 iii III
10 jjj JJJ


sed

# cat file1 | sed 's/BEFORE/AFTER/g': file1中のBEFOREをAFTERに一括置換
#  * s/BEFORE/AFTER/g: BEFOREをAFTERに置換(由来: substituteとglobal?)
#  * s/BEFORE/AFTER/: 1番目に出現したBEFOREをAFTERに置換
#  * viでも:%s/BEFORE/AFTER/gで一括置換できるので覚えとくと便利
#    (使用例: git rebase -i HEAD~5 -> viが開く -> :%s/pick/s/g で直近5コミットをまとめる)
#  * 一括置換だけじゃなく削除や部分置換もできるし、正規表現も使えます
#  * 元のファイルは変わりません(-iオプションを付けて上書きすることも可能)
#  * 私がさっと使えるのは一括置換ぐらいですが、シェル芸人はsedとawkを駆使してるイメージ
$ cat typo.txt  # スペルミスのあるファイル
Hello Wolrd!
Wolrd Wide Web

$ cat typo.txt | sed 's/Wolrd/World/g'  # スペルミスを直す
Hello World!
World Wide Web

$ cat typo.txt | sed 's/Wolrd/World/g' > typo_fixed.txt  # 直した結果を別ファイルに保存
$ cat typo_fixed.txt
Hello World!
World Wide Web

もっと知りたい方へ: sedでこういうときはどう書く?


awk

# cmd1 | awk '{print $5}': cmd1実行結果から、スペース区切りで5列目だけ表示
# cmd1 | awk -F ',' '{print $5}': cmd1実行結果から、カンマ区切りで5列目だけ表示
#  * ワンライナーの王様(私見)
#  * ifもforも変数も使えるし、区分としてはコマンドというよりプログラミング言語
#  * 〇〇区切りのn列目を抽出したい、みたいな要件だとドンピシャな言語
#  * なお、awkの文脈だと"n列目"じゃなくて"nフィールド目"と呼ぶ
#  * 筆者は、書けないけど、雰囲気読みだけできる人
#    (過去遭遇したレガシー案件で、独自書式のTSVをawkでパースしてXMLに変換してるのを見たことがあります)
#    (↑現職じゃないよ!)
#  * まれによく使われるので、教養として読めるようになってもいいかもしれないです
$ ls -l
total 0
drwxr-xr-x 1 arene arene 4096 Feb  4 22:40 abc
drwxr-xr-x 1 arene arene 4096 Feb  4 22:40 def
-rw-r--r-- 1 arene arene  134 Feb  4 22:50 file1.txt

arene@~/qiita/src $ ls -l | awk '{print $5}'  # 5列目だけ表示

4096
4096
134


xargs

# cmd1 | xargs cmd2: cmd1の実行結果をコマンドライン引数として受け取って、cmd2を実行
#  * cmd1 | cmd2は、cmd1の実行結果を「標準入力」として受け取ってcmd2を実行するのに対し
#    cmd1 | xargs cmd2は、cmd1の実行結果を「コマンドライン引数」として受け取ってcmd2を実行する
#  * 個人的にすごく好きなコマンド (うまく使えると賢くなった気持ちになれる)
#  * 慣れがいるけど、こいつがないとワンライナーできない要件が結構ある印象
#  * findの後に使うことが多い(複数ファイルに対する一括操作)
#  * 応用編のコマンドだし、分かりやすくて実用的な例は浮かばなかったです
$ ls -1
src
test
$ ls -1 | echo  # echoは標準入力を受け付けないので何も出ない

$ ls -1 | xargs echo  # xargsで引数として渡してやると表示できる
src test
$ ls -1 | xargs -n 1 echo  # -n 1をつけると1行ずつ渡すので、1行ずつechoされる
src
test


# 応用: 複数ファイルの一括リネーム
# find -type f dir_name | xargs -I{} mv {} {}.bak: dir_name以下のファイル全部に.bakを付ける
#  * この例だとrenameコマンドを使った方がラクなはず (なんかrenameに馴染めなくて個人的には使わない)
#  * mvやcpなど、受け取った文字列を2回以上使いたい場合は-I{}オプションを使う
$ tree  # 最初の状態
.
|-- src
|   |-- main.js
|   `-- sub.js
`-- test
    |-- main.js
    `-- sub.js

$ find test/ -type f  # findでtest以下のファイルの相対パスを表示
test/main.js
test/sub.js

$ find test/ -type f | xargs -I{} mv {} {}.test
  # 以下の内容に展開される (-I{}で、以降の{}が入力内容に置換されるようになる)
  # mv test/main.js test/main.js.bak
  # mv test/sub.js test/sub.js.bak
$ tree
.
|-- src
|   |-- main.js
|   `-- sub.js
`-- test
    |-- main.js.test
    `-- sub.js.test

$ find test/ -type f | sed 's/js.test/js/g' | xargs -I{} mv {}.test {} # 元に戻す
  # test/main.js.test をsedで test/main.js に置換したうえでmvに渡す
$ tree
.
|-- src
|   |-- main.js
|   `-- sub.js
`-- test
    |-- main.js
    `-- sub.js


less

# less file1: file1を見る(read only)
# cat file1 | cmd1 | cmd2 | less: file1をいろいろ加工した結果を見る
#  * ターミナルに出力せず、何かを見たいときにとりあえず使うコマンド
#  * 類似コマンドmoreの上位互換 (less is more!)
#  * read onlyなので安全安心 (変える気がないのにviで見るのはやめよう)
#  * viの一部キーバインドが使える
#    gg: 先頭行へ移動
#    G: 最終行へ移動
#    /pattern: patternでファイル内検索
#    q: 閉じる
#  * Fでtail -fと同様のこともできるし、名前に反してけっこう高機能
(例は下記リンク参照)

もっと知りたい方へ:
あなたはだんだん、ファイルを読むのにlessコマンドを使いたくなる
エンジニアなら知っておきたい lessコマンドtips 11選


>, >>(リダイレクト)

# cmd1 >> file1: cmd1の実行結果をfile1に書き出す(追記)
# cmd1 > file1: cmd1の実行結果をfile1に書き出す(上書き)
$ cat file1.txt  # リダイレクト前
1 aaa AAA
2 bbb BBB
3 ccc CCC

$ echo "4 ddd DDD" >> file1.txt  # リダイレクト(追記)
$ cat file1.txt
1 aaa AAA
2 bbb BBB
3 ccc CCC
4 ddd DDD

$ echo "4 ddd DDD" > file1.txt  # リダイレクト(上書き)
$ cat file1.txt
4 ddd DDD



# echo "echo login!" >> ~/.bashrc: bashrc末尾に設定を追加  ※実際に叩かないでください!
# * .bashrcはbashの設定ファイルです
# * >> でなく > を使うと、上書きされて設定が消えるので要注意
# * 手順書や環境構築自動化スクリプトで、設定ファイルを編集するときにリダイレクトを使います
#   (手動で環境構築するときは>>と>のうっかりミスが怖いため、普通にファイルを開いた方がいいと思う)



# something.sh > log.txt: something.shの実行結果(標準出力)をログ出力
# something.sh > log.txt 2>&1: something.shの実行結果(標準出力+標準エラー出力)をログ出力
# something.sh >/dev/null 2>&1: something.shの実行結果をどこにも出力しないようにする
$ cat something.sh  # 1行目で標準出力、2行目で標準エラー出力にメッセージの出るシェルスクリプト
#!/bin/bash
echo standard output
syntax-error!!!! # standard error

$ ./something.sh > log.txt  # 単にリダイレクトした場合、標準出力しかリダイレクトされない
./something.sh: line 3: syntax-error!!!!: command not found
$ cat log.txt
standard output

$ ./something.sh > log.txt 2>&1  # 2>&1を足すと、両方リダイレクトされる
$ cat log.txt
standard output
./something.sh: line 3: syntax-error!!!!: command not found

$ ./something.sh >/dev/null 2>&1  # 何も出ないようにする(いわゆる「デブヌルに投げる」というやつ)
$
$ ./something.sh 2>&1 >/dev/null  # なお逆にするとうまくいかない
./something.sh: line 3: syntax-error!!!!: command not found
#  解説
#  * 1: 標準出力   2: 標準エラー出力
#  * /dev/null: ゴミ箱的な、奈落的な、OSが用意する特別な空ファイル
#  * > log.txtは1>log.txtと同じで、標準出力をログ出力している
#  * > log.txt 2>&1は、2(標準エラー出力)を1(標準出力)の向き先(=ログファイル)へ向けている
#  * 2>&1 > log.txtでうまくいかないのは↓2つを逐次実行しているから
#    (1) 2>&1: 標準エラー出力を、デフォルトの標準出力(stdout)に切り替える
#    (2) > log.txt2: 標準出力をログファイルへ向ける
#    => 結果、標準エラー出力はデフォルトの標準出力へ、標準出力はログファイルへと出力される

もっと知りたい方へ: いい加減覚えよう。 command > /dev/null 2>&1の意味


インストールまわり

コマンド名 何ができる? コマンド名は何に由来している?
apt, yum コマンドのインストール Advanced Package Tool, Yellowdog Updater Modified
sudo ルート権限でコマンドを実行 superuser do(substitute user do)
su ユーザ切り替え substitute user
echo 文字列の表示 echo
env 環境変数の表示 environment
which, whereis コマンドの場所を探す which, where is
source, . 設定の反映(ファイル内容を現在のシェルで実行) source
chmod ファイル、ディレクトリのパーミッションを変更 change mode
chown ファイル、ディレクトリの所有者を変更 change owner
systemctl サービスの起動、停止など system control

apt, yum

# apt install git: gitをインストール(Ubuntuなど、Debian系OS)
# yum install git: gitをインストール(CentOSなど、RedHat系OS)
#  * しばしばsudo apt~形式で実行する
#  * 実行して「Permission Denied」「権限が足りません」系のメッセージがでたらとりあえずsudoする(雑)
#    (あるいはchown, chmodで権限を適切に設定する)
$ sudo apt install git
(大量のメッセージが出るが割愛)


sudo

# sudo cmd1: cmd1をrootユーザとして実行
#  * Ubuntuでは高頻度で使用 (Ubuntuはrootで作業しないでねという思想のため、一応従う)
#  * CentOSでは次のsuでrootユーザに切り替えるため、あまり使わない
#  * ユーザによっては実行できない場合がある
#    (セキュリティのしっかりした環境だとsudoできるユーザを制限している)
$ sudo vi /etc/hosts  # rootしか変更できない設定ファイルを編集
[sudo] password for arene:
$


su

# su user1: user1に切り替える(環境変数は現在のものを引き継ぐ)
# su - user1: user1に切り替える(現在の環境変数を捨てて、user1デフォルトの環境変数を利用)
# su -: rootユーザーに切り替える(現在の環境変数を捨てて、rootユーザーデフォルトの環境変数を利用)
#  * 私は常にハイフンを付けます
#    (環境変数を引き継ぐことによる思わぬミスを防ぐ意図)
#  * CentOSでsu - oracleとか、su - postgresとかよく使ってた
$ su -
Password:
#


echo

# echo abc: 文字列abcを出力
# echo $PATH: 環境変数PATHを出力
#  * 用途1: シェルスクリプトで使用方法やエラーメッセージを出力
#  * 用途2: 環境変数の確認
#  * コマンドを実行して「command not found」と出た場合は、大抵PATHが通ってないので、まずはPATHを通そう
$ echo abc
abc
$ echo $LANG
UTF-8

補足情報: PATHを通すとは、環境変数とは


env

# env | less: 環境変数を確認
#  * envだけでも見れるが、環境変数が多い場合見切れてしまうためlessで確認
#    -> lessを開いた状態で/PATHとすると、"PATH"で検索できます


which, whereis

# which cmd: cmdの実体が置かれている場所を表示
# whereis cmd: whichのちょっと詳しい版
#  * 正直whichしか使わない
#  * 複数バージョンのnodeをインストールしたんだけど、今動いてるやつの実体はどこにあるの?
#    いらないコマンドを削除したいんだけど、こいつどこにあるの?
#    とか、そういうケースで使う
$ which ls
/bin/ls

$ ls
access.log  error1.log  error2.log  src

$ /bin/ls
access.log  error1.log  error2.log  src


source, .

# source ~/.bashrc: .bashrcを再読み込み
# . ~/.bashrc: ↑と同じ(.はsourceのエイリアス)
#  * シェルの設定ファイルを変更した後の再読み込みで使うケースが100%(自分調べ)
#  * 一応、シェルスクリプトを実行することもできる
#
#  * sourceは、引数で指定したファイルを「現在のシェル」で実行するコマンド
#  * 普通にコマンドやシェルスクリプトを実行した場合は「新たに生成した別のシェル」で処理を実行している
#    (現在のシェルの変数が汚れないようになっている)
#  * これに対し、sourceでは現在のシェルで処理を行うため、
#    処理中に変更した環境変数やalias設定が、実行終了後も引き継がれる(=設定が反映される)
$ env | grep MY_ENV  # before

$ echo "export MY_ENV=abc" >> ~/.bashrc  # 適当な環境変数を足す
$ env | grep MY_ENV  # まだ反映されていない

$ . ~/.bashrc  # sourceでbashrcを再読み込み
$ env | grep MY_ENV  # ↑で設定した環境変数が反映されている
MY_ENV=abc


chmod

# chmod 755 *.sh: shファイルに実行権限を付与
# chmod 644 *.js: jsファイルを普通に読み書きできる設定にする
#  * 謎の数字にもちゃんと意味があるんだけど、正直644と755しか使わない
#  * wとかrとか文字でも設定できるけど、私は数字派
#  * プログラムを実行して「Permission denied」と出たときは大抵実行権限がないだけなので、755へ変更すればOK
#
# 一応説明すると
#  * 755のように数字を3つ並べるのは次の3つを指定している
#    [所有者に対する権限][所有グループに対する権限][その他に対する権限]
#  * 数字の意味は次の通り
#     0: 権限なし
#     1: 実行権限
#     2: 書き込み権限
#     4: 読み込み権限
#    (7=1+2+4で全部OK, 6=2+4で読み書きのみ、といった具合)
#  * つまり755は、"所有者はなんでもできる、他の人は読み書きだけできる"という設定
#         644は、"所有者は読み書きできる、他の人は読むことだけできる"という設定
$ ls -l  # before
total 0
-rw-r--r-- 1 arene arene 0 Feb  8 23:26 abc

$ chmod 755 abc  # 実行権限を付与
$ ls -l
total 0
-rwxr-xr-x 1 arene arene 0 Feb  8 23:26 abc

$ chmod 644 abc  # 実行権限をなくす
$ ls -l
total 0
-rw-r--r-- 1 arene arene 0 Feb  8 23:26 abc



# 応用: 一括変更
# find dir1 -type f | grep sh$ | xargs chmod 755: dir1以下のshすべてに実行権限を付与
#  * findでファイルの相対パスを探す -> grepで末尾がshで終わるファイルを探す -> 探したファイルをchmodする
#  * find dir1 -type f -name "*.sh" | xargs chmod 755 でも同じ

もっと知りたい方へ: Linuxの権限確認と変更(chmod)(超初心者向け)


chown

# chown user1:group1 file1: file1の所有者を変更(ユーザーをuser1, グループをgroup1にする)
# find dir1 | xargs chown user1:group1: dir1以下の全ファイルの所有者を一括変更
#  * ユーザ一覧の確認はcat /etc/passwd
#    (前半は各種ミドルウェアが足したユーザーなので、大抵は末尾数行を見れば事足りる)
#  * グループ一覧の確認はcat /etc/group (同上)
@@TODO: 具体例(いい感じに複数ユーザいる環境がなかった)


systemctl

#  * サービス、というのはfirewallやwebサーバみたいな、バックグラウンド実行されるプログラムのことを指す
#    (デーモンともいう)
#  * systemctlはサービスの起動停止などを行うコマンド
#  * これ系のコマンドは環境によって結構違うが、2020年現在、新しめの環境はsystemctl
#  * 古いlinuxだとserviceコマンド、chkconfigコマンドに分かれている
#  * Macにはないっぽい(最近デビューしたところなので知見ゼロ)
#  * 自作プログラムをサービスに登録することも勿論できます

# 起動、停止、現状確認
#  * 単に起動するだけだと、OS再起動したら止まってしまう点に注意
#  * サービス名はTabキーで補完できます
systemctl status service1      # service1の状態を確認 (生きてるか死んでるかを確認)
systemctl start service1       # service1を起動
systemctl stop service1        # service1を停止
systemctl restart service1     # service1を再起動 (停止->起動)

# 自動起動の設定
#  * enabled: OS起動時に自動起動する
#  * disabled: OS起動時に自動起動しない
systemctl list-unit-files      # サービスの一覧+自動起動するかどうかを表示
systemctl enable service1      # service1を自動起動するようにする
systemctl disable service1     # service1を自動起動しないようにする
systemctl is-enabled service1  # service1が自動起動する設定かどうか確認


OSまわり

コマンド名 何ができる? コマンド名は何に由来している?
date 時刻の確認、設定 date
df ディスク空き容量の確認 disk free
du ディレクトリのサイズを確認 disk usuage
free メモリの空き状況を確認 free
top CPUやメモリの使用状況を確認 ??
ps プロセス情報の確認 process status
kill PIDを指定してプロセスを停止させる(シグナルを送る) kill
pkill 指定したプロセス名を持つプロセスを一括で停止させる process kill?
pgrep 指定したプロセス名を持つプロセスのPIDを表示 pid grep
netstat ネットワークの状況を見る network status

date

# date: 現在時を表示
# date '+%Y%m%d %H:%M:%S': YYYYMMDD hh:mm:ss形式で現在時を表示
# date -s "YYYYMMDD hh:mm:ss": OS時刻を変更(由来: set)
#  * たまに使い、使うたびに書式どんなんだっけ? となるやつ
#  * date -s "YYYYMMDD hh:mm:ss"(OS時刻変更)とtouch -d "YYYYMMDD hh:mm:ss"(ファイルのタイムスタンプ変更)
#    が同じなので、これだけ覚えるようにして、あとは都度検索するスタイルにしています
$ date
Sun Feb  9 11:00:41 JST 2020

$ date '+%Y%m%d %H:%M:%S'
20200209 11:01:13

$ date -s "20200209 11:02:00"
Sun Feb  9 11:02:00 JST 2020

もっと知りたい方へ: date コマンドの日付指定頻出パターン


df

# df -h: ディスクの使用量/空き容量を単位付きで表示(由来: human readable)
# df: ディスクの使用量/空き容量を表示
#  * 基本は-hで見る
#  * hは値を丸めるため、正確な値を知りたい場合はオプション無しで叩く
#  * ファイルシステムが何か、それぞれどこにマウントされてるかも分かる
#  * ↓はWSLのUbuntuで見てるので、Cドライブがあったり、ちょっと変
@@TODO: 素のUbuntuでの実行結果に差し替える
$ df -h  # Use%が何%使ってるか。Used、Availがどれだけ使っているか、空いているか
Filesystem      Size  Used Avail Use% Mounted on
rootfs          230G  199G   31G  87% /
none            230G  199G   31G  87% /dev
none            230G  199G   31G  87% /run
none            230G  199G   31G  87% /run/lock
none            230G  199G   31G  87% /run/shm
none            230G  199G   31G  87% /run/user
cgroup          230G  199G   31G  87% /sys/fs/cgroup
C:\             230G  199G   31G  87% /mnt/c
E:\             223G  141G   83G  63% /mnt/e

$ df
Filesystem     1K-blocks      Used Available Use% Mounted on
rootfs         240312316 207873316  32439000  87% /
none           240312316 207873316  32439000  87% /dev
none           240312316 207873316  32439000  87% /run
none           240312316 207873316  32439000  87% /run/lock
none           240312316 207873316  32439000  87% /run/shm
none           240312316 207873316  32439000  87% /run/user
cgroup         240312316 207873316  32439000  87% /sys/fs/cgroup
C:\            240312316 207873316  32439000  87% /mnt/c
E:\            233322492 146962124  86360368  63% /mnt/e


du

# du -h: 各ディレクトリの容量を単位付きで表示(由来: human readable)
# du: 各ディレクトリの容量を表示
#  * lsではディレクトリのサイズを見ることができない
#  * 実サイズを見たいときはduを使う
#  * サブディレクトリが多くて見辛いときは適宜grepしたりlessしたりする
$ ls -lh  # lsだとディレクトリは一律4.0Kで表示され、実サイズは分からない
total 0
drwxr-xr-x 1 arene arene 4.0K Oct 14 08:53 dist
-rw-r--r-- 1 arene arene    0 Jan  1 10:10 file1.txt
drwxr-xr-x 1 arene arene 4.0K Oct 14 09:11 src

$ du -h  # dfで見ると、実サイズが分かる
0       ./dist/css
8.0K    ./dist/img
888K    ./dist/js
908K    ./dist
8.0K    ./src/assets
4.0K    ./src/components
4.0K    ./src/pages
16K     ./src
924K    .


free

# free -h: メモリ使用状況を単位付きで表示(由来: human readable)
# free: メモリ使用状況を表示
#  * OSによって若干表示内容が異なるらしい(新しい奴はavailableが出る)
#  * 紹介したものの、こう見ればOKという見方に自信がないです。。
#    (freeやavailableがある程度大きければ問題なし、くらいの認識)
#  * メモリが不足してないか判断するための正しい見方をご存じなら、ぜひコメントをください
$ free -h
              total        used        free      shared  buff/cache   available
Mem:           7.9G        6.8G        886M         17M        223M        980M
Swap:           24G        1.1G         22G
$ free
              total        used        free      shared  buff/cache   available
Mem:        8263508     7099428      934728       17720      229352     1030348
Swap:      25165824     1149132    24016692


top

# top: CPUやメモリの使用状況を確認
#  * デフォルトだとCPU使用率の多いプロセスが上に来る
#  * %CPUがCPU使用率。どのプロセスが高負荷かを確認できる。
#  * 右上のload averageもちょくちょく見る
#    値がCPUのコア数を超えると高負荷状態(デュアルコアなら、2以上が高負荷)
#    cpuのコア数は cat /proc/cpuinfo で確認可能
#  * ただ、4コアでload averageが3だったとしても
#    core1にタスクが集中していて、core1だけ高負荷状態、ということがありえる。
#    ↑のload averageがコア数未満ならOK、というのはあくまで一応の目安
$ top
top - 12:06:17 up 87 days, 11:55,  0 users,  load average: 0.52, 0.58, 0.59
Tasks:  13 total,   1 running,  12 sleeping,   0 stopped,   0 zombie
%Cpu(s): 10.2 us,  8.0 sy,  0.0 ni, 81.7 id,  0.0 wa,  0.1 hi,  0.0 si,  0.0 st
KiB Mem :  8263508 total,  1821072 free,  6213084 used,   229352 buff/cache
KiB Swap: 25165824 total, 23985072 free,  1180752 used.  1916692 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 5310 arene     20   0   17620   2052   1516 R   1.0  0.0   0:00.18 top
    1 root      20   0    8896    172    136 S   0.0  0.0   0:00.21 init
   74 root      20   0   19464    504    448 S   0.0  0.0   0:00.01 sshd
 1862 root      20   0   57560    344    312 S   0.0  0.0   0:00.01 nginx
 1863 www-data  20   0   58204   1036    904 S   0.0  0.0   0:00.19 nginx
 1865 www-data  20   0   58204   1036    920 S   0.0  0.0   0:00.07 nginx
 1868 www-data  20   0   58204   1036    904 S   0.0  0.0   0:00.01 nginx
 1869 www-data  20   0   58204    948    856 S   0.0  0.0   0:00.00 nginx
 1920 root      20   0    8904    224    176 S   0.0  0.0   0:00.01 init
 1921 arene     20   0   17332   4032   3896 S   0.0  0.0   0:00.32 bash
 1996 root      20   0   20220   4204   4056 S   0.0  0.1   0:00.17 sshd
 2069 arene     20   0   20488   2092   1956 S   0.0  0.0   0:05.02 sshd
 2070 arene     20   0   18828   5628   5520 S   0.0  0.1   0:11.96 bash

もっと知りたい方へ:
マルチコア時代のロードアベレージの見方
topコマンドの使い方


ps

# ps -ef: 全てのプロセスの詳細な情報を見る(由来: every, full)
#  * 用途1: あるプロセスが生きてるかどうかチェック  (webサーバ起動してる?)
#  * 用途2: あるプロセスのPID(プロセスID)をチェック -> kill ${PID}
#  * 他にもいろいろ見られるんだろうけど、あまり知らないです
#  * 歴史的経緯でオプションが2系統に分かれてて超ややこしいけど、私は-efしか使わない
$ ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0  2019 ?        00:00:00 /init ro
root        74     1  0  2019 ?        00:00:00 /usr/sbin/sshd
root      1862     1  0  2019 ?        00:00:00 nginx:
www-data  1863  1862  0  2019 ?        00:00:00 nginx:
www-data  1865  1862  0  2019 ?        00:00:00 nginx:
www-data  1868  1862  0  2019 ?        00:00:00 nginx:
www-data  1869  1862  0  2019 ?        00:00:00 nginx:
root      1920     1  0 Feb04 tty1     00:00:00 /init ro
arene     1921  1920  0 Feb04 tty1     00:00:00 -bash
root      1996    74  0 Feb04 ?        00:00:00 sshd: arene [priv]
arene     2069  1996  0 Feb04 ?        00:00:04 sshd: arene@pts/0
arene     2070  2069  0 Feb04 pts/0    00:00:11 -bash
arene     5090  2070  0 11:13 pts/0    00:00:00 ps -ef


kill

# kill 123: プロセスIDが123のプロセスを停止させる(SIGTERMを送る)
# kill -9 123: プロセスIDが123のプロセスを問答無用で殺す(9はSIGKILLのシグナル番号)
# kill -KILL 123: -9と同じ
#  * 名前の通り、プロセスを殺す目的で使うケースが99%
#  * ただ、正確には特定のプロセスに任意のシグナルを送るコマンドで、
#    デフォルトではSIGTERM(由来: terminate)を送っている
#  * シグナルとは、OSが各プロセスへ割り込み処理を命令するために送る信号
#    (例えばCtrl+Cでコマンドを終了するときは、SIGINT(由来: interrupt)が送られている。
#     割り込み処理なので、無限ループするプログラムであっても終了できる)
#  * SIGKILL(9)は、一番強力なシグナルで、こいつを送ると問答無用でプロセスを殺せる
#  * SIGKILLは結構乱暴なので最終手段。ますは無印killを試そう。
#    (SIGKILLだと終了処理さえ許さずぶち切られるため、次回起動時に不都合が起きる場合がまれにある)
$ ps -ef | grep eternal_loop | grep -v grep  # 適当に書いた無限ループするプログラムのPIDを調べる
arene     5500  2070  0 13:00 pts/0    00:00:00 ./eternal_loop

$ kill 5500  # pidを指定してkillする
[1]+  Terminated              ./eternal_loop
$ ps -ef | grep eternal_loop | grep -v grep  # killされたことを確認
$

より詳しい情報:
Linux シグナルの基礎
SIGNAL Manページ


pkill

# pkill process_name_prefix: process_name_prefixで始まるプロセスすべてを終了させる
# pkill -9 process_name_prefix: process_name_prefixで始まるプロセスすべてを問答無用で終了させる
#  * シグナルについては前項(kill)を参照
#  * ヒットした奴は全部殺されるので、実行前にps -ef | grep process_name_prefix で
#    対象プロセスを確認するのがベター
$ ps -ef | grep eternal_loop | grep -v grep  # 無限ループするプロセスがたくさんある
arene     5558  2070  0 13:13 pts/0    00:00:00 ./eternal_loop
arene     5562  2070  0 13:13 pts/0    00:00:00 ./eternal_loop2
arene     5566  2070  0 13:13 pts/0    00:00:00 ./eternal_loop3
arene     5570  2070  0 13:13 pts/0    00:00:00 ./_bak_eternal_loop

$ pkill eternal_loop  # pkillでまとめて殺す
[1]   Terminated              ./eternal_loop
[2]   Terminated              ./eternal_loop2
[3]-  Terminated              ./eternal_loop3

$ ps -ef | grep eternal_loop | grep -v grep  # 前方一致で一致した3つは殺された
arene     5570  2070  0 13:13 pts/0    00:00:00 ./_bak_eternal_loop


pgrep

# pgrep process_name_prefix: process_name_prefixで始まるプロセスすべてのPIDを出力
#  * 主にシェルスクリプトやワンライナーでPIDを動的に抽出して使いたいときに使用
#  * $()(コマンド置換)やxargsと組み合わせることが多い
$ ps -ef | grep eternal_loop | grep -v grep  # 無限ループするプロセスがたくさん
arene     5570  2070  0 13:13 pts/0    00:00:00 ./_bak_eternal_loop
arene     5590  2070  0 13:18 pts/0    00:00:00 ./eternal_loop
arene     5594  2070  0 13:18 pts/0    00:00:00 ./eternal_loop2
arene     5598  2070  0 13:18 pts/0    00:00:00 ./eternal_loop3

$ pgrep eternal_loop  # プロセスIDを抽出
5590
5594
5598
$ pgrep eternal_loop | xargs kill  # 抽出したプロセスを殺す
[5]   Terminated              ./eternal_loop
[6]-  Terminated              ./eternal_loop2
[7]+  Terminated              ./eternal_loop3

$ ps -ef | grep eternal_loop | grep -v grep  # 死亡確認
arene     5570  2070  0 13:13 pts/0    00:00:00 ./_bak_eternal_loop


netstat

# netstat -anp| less: ネットワークの状態を確認
#  * -a: すべての接続を表示(由来: all)
#  * -n: 名前解決しないで素のIPアドレス、ポート番号を表示(由来: number?)
#  * -p: プロセスIDを表示(由来: process)
#  * いろいろ見られるんだろうけど、私は-anpしか使わない
#  * LISTENING, ESTABLISHED, TIME_WAITなど各ポートの状況を確認できて素敵
@@TODO: ubuntuでの実行結果を貼る(手元のWSL環境だと見れなかった)

より詳しい情報: TCP/IP通信の状態を調べる「netstat」コマンドを使いこなす


その他

コマンド名 何ができる? コマンド名は何に由来している?
find ファイルやディレクトリを探す(パスの出力) find
history コマンド履歴の参照 history
diff 差分確認 difference
jobs 実行中のジョブを確認 jobs
bg 指定したジョブをバックグラウンドに移す background
fg 指定したジョブをフォアグラウンドに移す foreground
& バックグラウンド実行
&&,
$(), <() コマンド置換、プロセス置換
$? 直前のコマンドの終了ステータスを確認
for ループ処理

find

# find dir1 -type f: dir1以下のファイル一覧を表示
# find dir1 -type f -name "*.js": dir1以下のjsファイルの一覧を表示
# find dir1 -type d: dir1以下のディレクトリ一覧を表示
#  * オプションが豊富で、n階層下まで探す、特定の日時より古いファイルだけ探す、
#    特定のパーミッションのファイルだけ探す、とかいろいろできる
#  * だけど忘れちゃうので、さっと書けるのはこのくらい
#  * lsと違ってファイルパスが出力されるため、find xxx | xargs rm -rf みたいに一括操作に向いている
$ find src/ -type f
src/App.vue
src/assets/logo.png
src/components/HelloWorld.vue

$ find src/ -type f -name "*.png"
src/assets/logo.png

$ find src/ -type d
src/
src/assets
src/components

もっと知りたい方へ: findコマンドで覚えておきたい使い方12個


history

# history | less: コマンド履歴を確認
#  * 用途1: がちゃがちゃ環境構築してて、結果うまく行ったんだけど結局何をどうしたんだっけ? を調べる
#  * 用途2: 初めて入る謎のサーバの用途を調べる
#  * 用途3: よく使うけどめっちゃ長くて覚えられないあのコマンドを探して再利用する
#  * 環境変数HISTSIZEでhistoryを保持する数を指定できる
#    デフォルト値は大概小さいので、大きくしておくと、何かあった時に幸せになれるかも
#  * sshやDB接続のパスワードをコマンド直打ちすると、historyを見て素抜かれるので気を付けよう
#  * 単にコマンドを再利用したいだけならCtrl+Rの方がおすすめ。
#    fzfを入れるとかなり使いやすくなります。
$ history | tail
 3325  find src/ -type f
 3326  find src/ -type d
 3327  find src/ -type f -name "*.png"
 3328  find src/ -type d | xargs ls
 3329  find src/ -type d | xargs ls-l
 3330  find src/ -type d | xargs ls -l
 3331  find src/ -type d | xargs -n 1 ls -l
 3332  find src/ -type d -ls
 3333  find src/ -type f -ls
 3334  history | tail

$ echo $HISTSIZE
10000


diff

# diff file1 file2: file1とfile2の差分を表示
# diff -r dir1 dir2: dir1とdir2の差分を表示(サブディレクトリもチェック)
#  * 環境構築とかで差分のあるなしをチェックしたい時によく使う
#  * 差分内容をしっかり見比べたい場合は、WinMergeなりMeldなり差分比較用のソフトを使った方がいい
$ ls
dist src
$ cp -pr src/ src2  # コピーして差分を見る => 差分なし(当たり前)
$ diff -r src src2

$ echo "abc" >> src2/App.vue  # わざと差分を作って差分を見る
$ diff -r src src2
diff -r src/App.vue src2/App.vue
17a18
> abc



# 応用編: ソートした結果同士を比較
$ cat unsort1.txt  # 1~5をランダムに並べたファイル
1
5
2
4
3

$ cat unsort2.txt  # 1~5をランダムに並べたファイル その2
1
2
3
5
4

$ diff <(cat unsort1.txt | sort) <(cat unsort2.txt | sort)  # sortした結果同士を比較すると差分なし
$
$ diff $(cat unsort1.txt | sort) $(cat unsort2.txt | sort)  # 似てるけどコマンド置換ではエラー
diff: extra operand '3'
diff: Try 'diff --help' for more information.
# 解説
#  * <(cmd): cmd1の実行結果を別コマンドの入力として扱う (プロセス置換)
#  * $(cmd): cmd1の実行結果を文字列として展開する (コマンド置換)
#  * プロセス置換を利用すると、ワンライナーで2つのファイルをソートした結果を比較できる
#  * プロセス置換を使わないと、一旦別ファイルに吐き出して...とやるので割とめんどくさい
#  * csvの比較などで活躍します
#  * <()はファイル的なものとして扱われる一方で、$()はコマンド中の文字列として展開される。
#    diffのようなファイルを引数に取るコマンドにはプロセス置換が適している。


jobs, fg, bg

# jobs: バックグラウンド実行中のジョブ一覧を表示
# fg 1: ジョブ1をフォアグラウンド実行に切り替える
# bg 1: ジョブ1をバックグラウンド実行に切り替える
#  * うっかりバックグラウンド実行してしまったプログラムをフォアグラウンドに戻すときに使う
#  * 代表ケースは、viでCtrl+Zを押したとき
#    (vi編集中にCtrl+Zを押すとジョブが停止され、どこにいったか分からなくなるのは初心者あるある。
#     そんなときは、落ち着いてjobs -> fgとすればOK)
#  * バックグラウンド実行すべきものをうっかり普通に実行した場合は、
#    Ctlr+Zで停止させて、jobs -> bgでバックグラウンド実行に切り替えられる
#    (...のだけど、いつもCtrl+Cで止めて、&を付けなおして再実行しちゃうので使ったことがない)
$ ./eternal_loop1 &  # 無限ループするプログラムをバックグラウンド実行
[1] 5906
$ ./eternal_loop2 &
[2] 5910
$ ps -ef | grep eternal_loop | grep -v grep
arene     5906  2070  0 18:29 pts/0    00:00:00 ./eternal_loop1
arene     5910  2070  0 18:29 pts/0    00:00:00 ./eternal_loop2

$ jobs  # jobsで見ると、2つバックグラウンド実行されていることが分かる
[1]-  Running                 ./eternal_loop1 &
[2]+  Running                 ./eternal_loop2 &

$ fg 2  # ジョブ番号2をフォアグラウンドに切り替える
./eternal_loop2
^C  # 無限ループして終わらないため、Ctrl+Cで終わらせる

$ jobs  # ジョブ番号2が終了したことを確認
[1]+  Running                 ./eternal_loop1 &
$ ps -ef | grep eternal_loop | grep -v grep
arene     5906  2070  0 18:29 pts/0    00:00:00 ./eternal_loop1


& (バックグラウンド実行)

# cmd1: cmd1をフォアグラウンドで実行
# cmd1 &: cmd1をバックグラウンドで実行
#  * 重たいバッチ処理や、一時的にwebサーバを動かしたいときは、
#    コマンドをバックグラウンド実行すると便利 (勿論、ターミナルをもう一つ立ち上げてもOK)
#  * 次の&&や、リダイレクトの2>&1と混同しやすいが別物
#  * この辺の記号系は慣れるしかない
$ ./eternal_loop1 &  # 無限ループするプログラムをバックグラウンド実行
[1] 6104

$ echo 123  # バックグラウンドで実行したため、他のコマンドを使える
123


&&, ||

# cmd1 && cmd2: cmd1が成功したら、cmd2を実行(cmd1が失敗したらそこで終わり)
# cmd1 || cmd2: cmd1が失敗したら、cmd2を実行(cmd1が成功したらそこで終わり)
#  * 用途1: ワンライナーでちょっとした逐次処理を書く
#  * 用途2: cmd1 || echo "error message"
#  * 実用的な例がパッとでてこないけど、ちょいちょい使うし、見かける
## 両方成功するケース
$ echo aaa && echo bbb
aaa
bbb
$ echo aaa || echo bbb
aaa

## 両方失敗するケース
$ echoooo aaa && echoooo bbb
echoooo: command not found
$ echoooo aaa || echoooo bbb
echoooo: command not found
echoooo: command not found


$(), <() (コマンド置換、プロセス置換)

# echo ${var1}: 変数var1の中身を出力 (変数展開)
# echo $(cmd1): cmd1の実行結果を出力 (コマンド置換)
# echo `cmd1`: ↑とだいたい同じ (コマンド置換(旧記法))
# diff <(cmd1) <(cmd2): cmd1とcmd2の実行結果を出力 (プロセス置換)
#  * ${}と$()は混同しやすい。jsのtemplateリテラルと同じ奴が変数置換
#  * $()は``の新記法。$(cmd1 $(cmd2))のようにネストさせやすいのが特徴
#    dateとかpgrepとか動的に変わる内容と組み合わせることが多い。
#  * <()はシェルスクリプトやワンライナーで、一時ファイルを使いたくなったときが使い時。
#    catやdiff, wihle read lineなど、ファイル内容を使う系のコマンドと組み合わせる。
$ cat lsByOption.sh  # ワンライナーでのいい例が浮かばなかったのでしょぼいシェルスクリプトを用意しました
#!/bin/bash
OPTION=$1
ls $(echo ${OPTION})  # 第1引数が-lなら、ls -lになる

$ ls  # 普通にlsを実行
lsByOption.sh  unsort1.txt  unsort2.txt

$ ./lsByOption.sh -l  # ls $(echo ${OPTION})がls -lになる
total 0
-rwxr-xr-x 1 arene arene 45 Feb  9 19:44 lsByOption.sh
-rw-r--r-- 1 arene arene 10 Feb  9 19:29 unsort1.txt
-rw-r--r-- 1 arene arene 10 Feb  9 19:30 unsort2.txt

$ ./lsByOption.sh -al  # ls $(echo ${OPTION})がls -alになる
total 0
drwxr-xr-x 1 arene arene 4096 Feb  9 19:44 .
drwxr-xr-x 1 arene arene 4096 Feb  9 19:28 ..
-rwxr-xr-x 1 arene arene   45 Feb  9 19:44 lsByOption.sh
-rw-r--r-- 1 arene arene   10 Feb  9 19:29 unsort1.txt
-rw-r--r-- 1 arene arene   10 Feb  9 19:30 unsort2.tx

もっと知りたい方へ:
コマンドとコマンドをつなぐ糊
bashのプロセス置換機能を活用して、シェル作業やスクリプト書きを効率化する


$?

# echo $?: 直前のコマンドの終了ステータスを表示
#  * シェルスクリプトで異常系の処理を書くときに使う?
#  * いきおいで挙げたけどあんまり使わないかも
$ echo 123  # OKケース
123
$ echo $?
0

$ hdskds  # NGケース
hdskds: command not found
$ echo $?
127


for

# for i in {1..10} ; do cmd1; done: cmd1を10回繰り返す
#  * よく使いたくなるけど、使うたびにどう書いたっけ? となるやる
#  * ワンライナーにするために無理やり1行にしてるけど、ちゃんと改行すると↓になる
#    for i in {1..10} ;
#    do
#      cmd1;
#    done
#  * {1..10}: ブレース展開(連番ver): 1 2 3 4 5 6 7 8 9 10に展開される
#  *          代わりに$(seq 10)でもOK
$ for i in {1..10} ; do echo $i; done
1
2
3
4
5
6
7
8
9
10


書かないことにしたやつ

よく使うけど流石にスコープ外だろう、まれに使うけど実はあんまり知らない
・・・といった理由で割愛したくなったコマンド達。
項目だけ挙げます。

コマンド名 何ができる? コマンド名は何に由来している?
vi ファイルの編集 visual editor(visual interface
make プログラムのコンパイル make
curl HTTPリクエストを出す command url??
rsync ネットワーク越しにディレクトリ内容を同期 remote synchronizer
ssh-keygen sshの秘密鍵、公開鍵を作る ssh key generator
npm nodeのパッケージをインストールするなど node package manager
git gitを使う(雑) イギリス英語のスラングでバカ <- はじめて知った


おまけ

全く使わないけど紹介してみたかったので、書きました。たのしかった。

コマンド名 何ができる? コマンド名は何に由来している?
nice ユーザーの優先度を調整
sl 汽車が出る lsの反対

nice

# nice -n 20 cmd1: cmd1を優先度20で実行
#  * linuxの各ユーザはnice値という優先度を持っている
#    -20(優先度最高)~20(優先度最低)
#  * CPU負荷の高いバッチ処理を、他プロセスの隙間時間に実行したいときに使えるかも(経験ないです)
#  * 高野豊さんの「rootから/へのメッセージ」というエッセイ本で知りました。
#    日本にunixが来た当初、cpuリソースは貴重だったため
#    重たい処理をバンバン走らせる素行の悪いユーザに対して、root管理者はnice値を上げて対抗していたそうです。
#  * このエピソードが何故か分からないけど私はすごく好き
$ nice -n 20 ls
src dist

参考: rootから/へのメッセージ(Amazon)


sl ※インストールが必要

実行するとターミナルに汽車が走ります。
typoしたときは汽車でもみて落ち着こう。
(ご丁寧にCtrl+Cを無効化してるあたり、ユーモアにあふれてて素敵)

image.png

もっと知りたい方へ: 仕事で役に立たない!Linuxネタコマンド集


最後に

コマンドライン操作は、楽しいです。
また、Linux(Unix)のシステムまわりの話も面白いです。
この記事を通して、ちょっとでも発見や興味の広がりがあれば幸いに思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ただのメモ

CentOS 7での使用したコマンド一覧

自分用の備忘録として残しますが、多少解説つきで交換いたします。
使用したコマンドはどんどん記載していくので随時更新予定です。

scpコマンドによるデータダウンロード、アップロード

こちらはssh接続が可能なリモートホスト間でのファイル転送コマンドです。

まずは、リモートからssh接続先のサーバーへファイルをアップロードする方法です。ssh接続していたら抜けてくださいね。

# scp xxx.txt root@<ipアドレス>:/tmp
// rootユーザーの/tmpディレクトリに xxx.txtをアップロードします

次に、ssh接続先からファイルをダウンロードする方法です。

# scp root@<ipアドレス>: /tmp/xxx.txt /Users/Admin/Desktop
// tmpディレクトリのxxx.txtファイルをDesktop上に置く(Macです)

IPアドレスの固定orDHCPの設定変更

ESXi上のCentOSのIPアドレスを固定したかったのでファイルを編集しました。

# ls /etc/sysconfig/network-scripts/
ifcfg-ens160     ifdown-eth   ifdown-routes  ifup-aliases  ifup-plip    ifup-tunnel
ifcfg-lo         ifdown-ippp  ifdown-sit     ifup-bnep     ifup-plusb   ifup-wireless
ifdown           ifdown-ipv6  ifdown-tunnel  ifup-eth      ifup-post    init.ipv6-global
ifdown-Team      ifdown-isdn  ifup           ifup-ippp     ifup-ppp     network-functions
ifdown-TeamPort  ifdown-post  ifup-Team      ifup-ipv6     ifup-routes  network-functions-ipv6
ifdown-bnep      ifdown-ppp   ifup-TeamPort  ifup-isdn     ifup-sit

#vim /etc/sysconfig/network-scripts/ifcfg-ens16
TYPE="Ethernet"
PROXY_METHOD="none"
BROWSER_ONLY="no"
BOOTPROTO="static" #dhcp->static //変更
IPADDR=192.168.1.96 #add //追加
NETMASK=255.255.255.0 #add //追加
GATEWAY=192.168.1.1 #add //追加
DNS1=192.168.1.1 #add //追加
DEFROUTE="yes"
IPV4_FAILURE_FATAL="no"
IPV6INIT="yes"
IPV6_AUTOCONF="yes"
IPV6_DEFROUTE="yes"
IPV6_FAILURE_FATAL="no"
IPV6_ADDR_GEN_MODE="stable-privacy"
NAME="ens160"
UUID="53806499-7069-49cb-a15c-fd7e9eb89809"
DEVICE="ens160"
ONBOOT="yes"
#service network restart
//サービスを再起動
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む