OpenVessel

シェルとコマンド

このページの見出し
  1. 概要
  2. sh の起動
  3. 文法
  4. クォート
  5. 展開
  6. リダイレクト
  7. 条件式
  8. コマンドの実行
  9. シェルのオプション
  10. 対話モード
  11. 組み込みコマンド
  12. プログラム

このページは、シェル sh(bash と dash としても入っています)と、カーネルに組み込まれたコマンドラインのプログラムすべての仕様です。パーサが受け付ける文法、展開、リダイレクト、組み込みコマンド、オプション、終了ステータス、対話モード、そして各プログラムのオプションと GNU coreutils との違いを説明します。

概要

このページのシェルとプログラムはカーネルのプログラムです。カーネルの worker の中で動く TypeScript の関数で、別の Web Worker ではありません。自分の worker で動くのは node(と、それが起動するもの)だけです(プロセス と Node.js の層 を参照)。カーネルのプログラムも本物のプロセスです。pid・親・ファイル記述子・環境変数・終了ステータスを持ち、ps に表示されます。

項目値
ソースsrc/kernel/sh/parse.ts(パーサ)、interp.ts(インタプリタ・組み込みコマンド・行編集)、arith.ts、pattern.ts、printf.ts、test.ts。プログラムは src/kernel/coreutils.ts と src/kernel/curl.ts
シェルの名前/bin/sh・/bin/bash・/bin/dash。どれも同じプログラムです。bash は GNU bash ではなく、ここで説明する同じ範囲を受け付けます。
プログラムの置き方各プログラムは、中身が #!/ov/builtin <name> の実行可能なファイル(モード 0755)です。カーネルはこのインタプリタ行を見分けて、組み込みの関数を動かします。
場所シェルと coreutils は /bin。curl・wget・env(#!/usr/bin/env の行のための 2 つ目)・node・nodejs(node へのシンボリックリンク)・npm・npx・pnpm・pnpx・yarn・bun・bunx・corepack は /usr/bin
既定の PATH/usr/local/bin:/usr/bin:/bin
vessel.exec(line)/bin/sh -c <line> を動かし、{ code, output } で解決します(ホスト API を参照)
端末vessel.spawn('sh', [], { terminal: { cols, rows } }) で sh の fd 0〜2 が TTY になり、対話モードになります

ホストが起動したプロセスは、カーネルの既定の環境変数に env オプションを重ねたものを受け取ります。作業ディレクトリは cwd を指定しなければ /home/user です(無ければ作ります)。既定の環境変数は次のとおりです。

変数値
PATH/usr/local/bin:/usr/bin:/bin
HOME/home/user
USER、LOGNAMEuser
SHELL/bin/sh
TERMxterm-256color
LANGen_US.UTF-8
TMPDIR/tmp
HOSTNAMEopenvessel
NAPI_RS_FORCE_WASI1

sh の起動

sh [options] [-c command [name [arg...]]]
sh [options] script [arg...]
sh [options]                     # 標準入力からコマンドを読む。TTY なら対話モード

オプションは左から順に、次のどれでもない引数が来るまで読みます。

オプション効果
-c commandcommand を動かします。オプションの読み取りはここで終わります。次の引数が $0(既定は sh)、残りが $1… になります。
-i標準入力が TTY でなくても対話モードにします。
-e・-u・-x・-f・-a と + の形set と同じです(シェルのオプション を参照)。複数の文字をまとめられます(-eu、+ex)が、使える文字はこの 5 つだけです。-ec は分けられず、スクリプトの名前として扱われます。
-o name、+o nameset -o name / set +o name と同じです。
-l、--login、--norc、--noprofile、-s受け付けて無視します。
--オプションの終わりです。

それ以外(--version も含む)はスクリプトのファイル名として扱います。

何を動かすかは次のように決まります。

状況動作
-c がある文字列を解析して動かします。終了ステータスは最後のコマンドのものです。
スクリプトの引数があるファイルを読みます(作業ディレクトリから見た相対パス。PATH は探しません)。1 行目が #! で始まっていれば捨てます。$0 は渡されたとおりのファイル名、$1… は残りの引数です。ファイルが無いと sh: <file>: No such file or directory を出し、127 で終わります。
引数なし・標準入力が TTY でない・-i なし標準入力をすべて読んでから、1 つのスクリプトとして解析して動かします。
引数なしで標準入力が TTY、または -i$HOME/.shrc を動かし(エラーは無視)、対話のループに入ります(対話モード を参照)。

/home/user/.shrc は起動時に次の内容で作られます。

alias ll='ls -l'
alias la='ls -la'

.shrc を読むのは対話シェルだけなので、sh -c やスクリプトにはこの別名がありません。

文法の誤りは <name>: <message>(<name> は $0 かスクリプトの名前)と表示され、入力をどこも動かさずにステータス 2 で戻ります。

文法

パーサ(src/kernel/sh/parse.ts)は、POSIX シェルの文法の実用的な部分に、bash の拡張をいくつか加えたものです。入力全体を解析してから動かします(対話モードでは、完結したコマンド行ごと)。

リストとパイプライン

構文意味
a ; b、改行a を動かし、次に b を動かします。
a & ba をバックグラウンドで動かし(バックグラウンドとサブシェル を参照)、次に b を動かします。
a && ba が 0 で終わったときだけ b を動かします。
a || ba が 0 以外で終わったときだけ b を動かします。
a | bパイプライン。a の標準出力が b の標準入力になります。要素は並行に動きます。
a |& b同じですが、a の標準エラー出力もパイプに入ります(a 2>&1 | b の略記)。
! pipeline終了ステータスを反転します(0 は 1 に、それ以外は 0 に)。! の後には空白が必要です。

&& と || は同じ優先順位で、左から結合します。|・&&・|| の後には改行を置けます(次の行に続きます)。シングルクォートの外では、\ と改行の組は取り除かれます(行の継続)。単語の先頭の # から行末まではコメントです。

複合コマンド

構文説明
{ list; }中かっこのグループ。今のシェルで動きます。{ と } は予約語なので、独立した単語でなければならず、} はコマンドの先頭(; か改行の後)に置きます。
( list )サブシェル。シェルの状態(変数・関数・別名・オプション・作業ディレクトリ・fd)のコピーで動きます。変更は親に届きません。
if list; then list; [elif list; then list;]... [else list;] fi
while list; do list; done
until list; do list; done
for name [in word...]; do list; donein が無ければ "$@" を回します。単語はすべての展開(分割とグロブも)を受けます。
case word in [(]pattern[|pattern]...) list ;; ... esacシェルのパターンです(パターン照合 を参照)。最初に合った項目を動かします。どれにも合わなければステータスは 0 です。esac の前の最後の ;; は省けます。
(( expression ))算術のコマンド。式が 0 以外ならステータス 0、そうでなければ 1 です。中身はまずダブルクォートの文字列と同じように展開されます。
[[ expression ]]条件のコマンド(条件コマンド を参照)。

どの複合コマンドの後にもリダイレクトを書けます(while ...; done < file、{ ...; } 2>&1)。

;;& は case の区切りとして受け付けますが、;; と同じ動きです(その先のパターンは調べません)。コマンドの直後の ;& は文法の誤りで、次の項目への流れ込み(fallthrough)はありません。

関数

name() compound-command
function name compound-command
function name() compound-command
  • name() の形では、name はそのままの文字の単語でなければなりません。function を使う形では、名前に英字・数字・_・.・:・- を使えます(先頭は英字か _)。
  • 本体はどのコマンドでもかまいません。ふつうは { ...; } のグループです。
  • 関数を呼ぶと、その間だけ $1… と $# が変わり($0 は変わりません)、終わると元に戻ります。local と declare の変数は関数から戻るときに元に戻ります。
  • return [n] はステータス n & 255(既定は直前のステータス)で関数を抜けます。
  • 呼び出しが 200 段より深くなると maximum function nesting level exceeded で失敗します。
  • コマンド名を探す順番は、関数、組み込みコマンド、プログラムのファイルです(コマンドの探し方 を参照)。

予約語

if then else elif fi do done case esac while until for in { } ! function [[ ]] は、コマンドの位置でだけ予約語になります(in と do は文法がそれを待つ位置で)。select・coproc・time は予約語ではありません(time はプログラムです。time と timeout を参照)。

対応していないもの

パーサとインタプリタで確かめたものです。

機能どうなるか
中かっこの展開 {a,b}、{1..3}行いません。単語はそのまま残ります(echo {a,b} は {a,b} を出力)。
プロセス置換 <(cmd)、>(cmd)文法の誤り: syntax error near unexpected token '('。
配列name=(a b c) は、かっこの中の文字列(最初の ) まで。前後の空白を除き、クォートは処理しない)を 1 つの文字列として持ちます。${name[i]} は添字を無視します。name+=(x) は区切りなしで文字列を足します。
間接展開 ${!name}bad substitution(文法の誤り、ステータス 2)。
${parameter@op}、${!prefix*}bad substitution。
case の流れ込み ;&文法の誤り(上を参照)。
拡張グロブ @(…)、!(…)、+(…)文法の誤り。( は単語の中でメタ文字です。
ジョブ制御fg・bg はありません(command not found)。jobs は何も出力しません。
traptrap はハンドラを記録しますが、実行はしません(EXIT・INT・ERR のハンドラは動きません)。
PS2続きの行のプロンプトは常に > です。

クォート

形意味
'...'そのままの文字列です。特別な文字はありません。中に ' は書けません。
"..."$・`・\ は特別なままです。\ がエスケープするのは $・`・"・\・改行(取り除かれる)だけで、ほかの文字の前ではそのまま残ります。
\c(クォートの外)次の 1 文字をクォートします。\ と改行の組は行の継続です。
$'...'ANSI-C のクォート。エスケープは \n・\t・\r・\e/\E(ESC)・\0(NUL)・\xHH(16 進 1〜2 桁)・\uHHHH(16 進 1〜4 桁)です。それ以外の \c は c になります(\\ → \、\' → '。\a・\b・\f・\v は文字そのものになる点に注意)。

クォートされた文字列は、分割もグロブもされません。空のクォート(""、'')は空の単語を 1 つ作ります。

展開

単純なコマンドの各単語は、次の順に展開されます。チルダ、パラメータ・コマンド置換・算術(左から順に)、クォートされていない結果の単語分割、クォートされていないグロブ文字を含む単語のパス名展開、クォートの除去です。代入の値、リダイレクトの対象、case の単語、[[ ]] の中の単語は、単語分割もグロブもせずに展開します。

別名の置き換えは展開の後に、最初の単語にだけ行います(別名 を参照)。

チルダ展開

単語の先頭の ~ の後が、単語の終わり・/・メタ文字のとき、置き換えます。

形結果
~$HOME(設定されていなければ /home/user)
~root/root
~name/home/name(パスワードファイルは見ません)

結果は分割もグロブもされません。~+ と ~- には対応していません。展開されるのは単語の先頭の ~ だけです(PATH=~/bin は展開され、a:~/b の 2 つ目の ~ は展開されません)。

パラメータと変数

変数名は [A-Za-z_][A-Za-z0-9_]* です。$name は、この形でいちばん長い名前を取ります。$1〜$9 は 1 桁だけ取ります($10 は $1 の後に 0)。10 番目以降は ${10} と書きます。

特別なパラメータ:

パラメータ値
$?直前のコマンドのステータス。
$$シェルのプロセスの pid。サブシェル・パイプラインの要素・コマンド置換は同じプロセスの中で動くので、同じ pid になります。
$!最後のバックグラウンドのジョブの識別子。内部の番号で、カーネルの pid ではありません。kill ではなく wait に使います。最初の & の前は空です。
$#位置パラメータの数。
$0シェルかスクリプトの名前(sh、-c の名前の引数、またはスクリプトのパス)。
$1…位置パラメータ。
$@、$*すべての位置パラメータ(単語分割 を参照)。
$-今のオプションの文字。e・x・u・i から(この順で)。f・a・pipefail は表示されません。

シェルが設定する、または読む変数:

変数意味
PWD今のディレクトリ。常にシェルの作業ディレクトリを表し、エクスポートされます。PWD に代入すると、シェルの作業ディレクトリの認識が変わります。
OLDPWD前のディレクトリ。cd が設定し、エクスポートします。
PPIDシェルのプロセスの親の pid。
IFS単語の区切り文字。既定は空白・タブ・改行。エクスポートされません。
HOME~、引数なしの cd、プロンプトの ~、.shrc に使います。
PATHプログラムを探すパス(source・type・command -v・which・タブ補完でも使います)。
CDPATHcd が、. で始まらない相対の名前を探すディレクトリ(コロン区切り)。
RANDOM読むたびに 0〜32767 の新しい乱数。
SECONDSシェルのプロセスが始まってからの秒数(整数)。
PS1対話モードのプロンプト(プロンプト を参照)。
PS4set -x のトレース行の前に付く文字列。既定は + 。
REPLYread の既定の変数。
OPTIND、OPTARGgetopts が使います。
BASH_REMATCH[[ string =~ regex ]] が、合った部分の文字列全体を入れます(配列ではなくただの文字列)。
__OV_OPTSUB、__OV_DIRSTACKgetopts と pushd/popd の内部の状態(set で見えます)。

代入:

  • name=value と name+=value(今の値に足す)。コマンドの前に複数置けます。
  • コマンドの単語が無いときは、今のシェルで代入します。ステータスは、シェルが最後に動かしたコマンド置換のものです。前のコマンドのものでも当てはまります(x=$(false); y=1 の y=1 は 1)。まだ 1 つも動かしていなければ 0 です。
  • 外部のプログラムの前に置くと、そのプログラムの環境変数にだけ入ります。
  • 関数やふつうの組み込みコマンドの前に置くと、呼び出しの間だけ設定され、その後元に戻ります。
  • 特別な組み込みコマンド(: . break continue eval exec exit export readonly return set shift trap unset source)の前に置くと、設定されたまま残ります。
  • readonly の変数に代入すると sh: <name>: readonly variable になります。代入だけのコマンドでは、対話でないシェルはステータス 1 で止まります。組み込みコマンド(export・read など)の中では、そのコマンドがステータス 1 で失敗します。

パラメータ展開

以下の parameter は、名前、位置の番号、または @ * # ? $ ! - のどれかです。

形結果
${parameter}値。
${#parameter}長さ(UTF-16 のコード単位の数)。${#@} と ${#*} は位置パラメータの数です。
${p:-word} / ${p-word}p が未設定か空 / 未設定なら word。そうでなければ値。
${p:=word} / ${p=word}同じで、さらに p に word を代入します。
${p:?word} / ${p?word}未設定か空 / 未設定なら、sh: p: word(または sh: p: parameter null or not set)を出して、シェルをステータス 1 で終わらせます。対話シェルも終わります。
${p:+word} / ${p+word}p が設定されていて空でない / 設定されているなら word。そうでなければ空。
${p#pattern} / ${p##pattern}先頭から、合う部分の最短 / 最長を取り除きます。
${p%pattern} / ${p%%pattern}末尾から、合う部分の最短 / 最長を取り除きます。
${p/pattern/string}最初に合った部分を置き換えます(いちばん左の位置での最長一致)。
${p//pattern/string}合った部分をすべて置き換えます。
${p/#pattern/string} / ${p/%pattern/string}先頭 / 末尾に固定した一致を置き換えます。
${p^} / ${p^^}最初の文字 / すべての文字を大文字にします。パターンの引数は無視します。
${p,} / ${p,,}最初の文字 / すべての文字を小文字にします。
${p:offset} / ${p:offset:length}部分文字列。負の offset は末尾から数えます(${p: -3} と書きます。${p:-3} は既定値の形です)。負の length は、末尾からその文字数手前で終わります。offset と length は展開後に Number() で変換します。算術式ではなく、数でなければ 0 です。@/* では、パラメータの切り出しではなく、つないだ文字列の部分文字列になります。
${name[index]}添字は無視します。${name} と同じです。

#・%・/ の形のパターンはシェルのパターンで、クォートした部分は文字どおりに照合します。word にはふつうの展開が行われます。set -u のとき、未設定の変数(特別なパラメータを除く)を展開すると sh: name: unbound variable を出し、ステータス 1 で終わります(対話シェルも終わります)。

コマンド置換

$(commands) と `commands` は、シェルのコピー(同じプロセスの中)でコマンドを動かし、標準出力を取り込みます。末尾の改行は取り除きます。クォートしなければ、結果は単語分割とグロブを受けます。$? は置換のステータスになります。バッククォートの中では、解析の前に \$・\`・\\ のエスケープを外します。$(( は常に算術展開の始まりです。

算術展開

$(( expression )) は 10 進の値に置き換わります。式はまずダブルクォートの文字列と同じように展開され($x や $(cmd) が使えます)、その後 src/kernel/sh/arith.ts で計算されます。

オペランド:

オペランド例
10 進42
8 進(先頭が 0)010 = 8。08 はエラー
16 進0x1f
base#digits(基数 2〜36)2#101 = 5、36#z = 35
変数名未設定か空なら 0。値が数でなければ式として計算します。値が変数名なら、それをたどります(8 段まで)。

演算子(優先順位の高い順):

演算子説明
( )グループ
x++ x-- ++x --x変数にだけ使えます
+ - ! ~(単項)
**右結合
* / %/ は 0 の方向に切り捨て。0 による / と % は division by 0 で失敗
+ -
<< >>
< <= > >=結果は 1 か 0
== !=
&
^
|
&&両辺を常に計算します
||両辺を常に計算します
?:
= += -= *= /= %= <<= >>= &= ^= |= **=変数への代入
,最後の式の値

数は 64 ビットの整数ではなく JavaScript の double です。* は 32 ビットで桁あふれして掛け算します(2147483647*2 = -2)。ビット演算子 << >> & ^ | ~ は 32 ビットの整数で計算します(1<<33 = 2)。 +/- は桁あふれしません(2147483647+1 = 2147483648)。

$(( )) の中のエラー(文法の誤り、0 による割り算)は sh: <expression>: <message> と表示され、対話でないシェルはステータス 1 で止まります。(( )) では sh: ((: <message> と表示し、コマンドが 1 を返します。 let では sh: <message> と表示し、対話でないシェルはステータス 1 で止まります。

単語分割

クォートされていないパラメータ展開・コマンド置換・算術展開の結果は、IFS の文字で単語に分けられます(既定は空白・タブ・改行。IFS が未設定なら既定と同じ)。

  • IFS の空白(空白・タブ・改行)の並びは単語を区切り、両端では取り除かれます。
  • それ以外の IFS の文字は 1 つごとに単語を終わらせるので、続けて並ぶと空の単語ができます(IFS=: で a::b は a、空、b)。
  • IFS='' で分割しなくなります。
  • 同じ単語の中でクォートされていた部分は分割されません。

"$@" は位置パラメータ 1 つにつき 1 単語に展開されます(無ければ何も無し)。"$*" は IFS の最初の文字でつないだ 1 単語です(IFS が空なら区切りなし)。クォートしない $@ と $* は、パラメータ 1 つにつき 1 単語になり、それぞれがさらに分割されます。

パス名展開

分割の後、クォートされていない *・?・[ を含む単語を、ファイルシステムと照合します。照合はパスの要素ごとです。

パターン合うもの
*/ を含まない任意の文字列
?/ 以外の任意の 1 文字
[abc]、[a-z]集合の中の 1 文字
[!abc]、[^abc]集合に無い 1 文字
[[:class:]]alpha・digit・alnum・upper・lower・space・blank・punct・xdigit・word
**(要素全体)0 個以上のディレクトリ、深さは任意(. で始まるディレクトリは飛ばします)
\c やクォートした文字その文字そのもの
  • . で始まる名前は、パターンの要素が . で始まるときだけ合います。. と .. は readdir に出てこないので、合うことはありません。
  • 結果は並べ替えられます(UTF-16 のコード単位の順)。パターンが / で終わると、すべての結果の後ろに / が付きます。ディレクトリだけに絞るわけではありません(*/ はファイルも f1/ のように並べます)。
  • 何も合わなければ、単語はそのまま残ります(echo *.nomatch は *.nomatch を出力)。nullglob・failglob はありません。
  • set -f(または set -o noglob)でパス名展開をしなくなります。

パターン照合

case、[[ x == pattern ]]、パラメータ展開の #/%// の形は、グロブと同じパターンの書き方を使います。ただし * と ? は / にも合い、先頭のドットも特別扱いしません。閉じる ] の無い [ は、[ そのものに合います。

別名

alias name=value で別名を定義します。単純なコマンドを動かすとき、最初の単語(展開の後)が別名なら、その値を単純なコマンドとして解析し、その単語で最初の単語を置き換えます。このため次のようになります。

  • 値が 1 つの単純なコマンドの別名だけが動きます。;・|・&& や複合コマンドを含む値は、何にも展開されません。
  • 別名は再帰的には展開されません。別名を定義したのと同じ行で、その別名を使えます。
  • $HOME/.shrc で定義した別名(既定では ll と la)は対話シェルにだけあります。

リダイレクト

リダイレクトの前には、1 桁のファイル記述子の番号(0〜9)を、演算子の直前に書けます(2>)。リダイレクトは左から順に適用します。対象の単語は、分割もグロブもせずに展開します(> *.txt は *.txt という名前のファイルを作ります)。

演算子既定の fd効果
< file0読み込み用に開く
> file1書き込み用に開く。作成し、切り詰める
>| file1> と同じ(noclobber はありません)
>> file1追記用に開く。作成する
<> file0読み書き用に開く。作成する(切り詰めない)
&> file、>& file1 と 2標準出力と標準エラー出力をファイルへ(切り詰める)
&>> file1 と 2標準出力と標準エラー出力をファイルに追記
n>&m、n<&m1 / 0n を m の複製にする
n>&-、n<&-1 / 0n を閉じる
<< word、<<- word0ヒアドキュメント
<<< word0ヒアストリング。展開した単語に改行を付けたもの

エラーのときは sh: <target>: No such file or directory(または Is a directory、Permission denied、閉じた fd の複製なら <n>: Bad file descriptor)を出し、コマンドは動かずにステータス 1 で失敗します。数でない単語への n<&word は ambiguous redirect です。

コマンドの無い exec にリダイレクトを付けると、シェル自身にずっと適用されます。

exec 3>/tmp/log      # fd 3 を開く
echo hello >&3
exec 3>&-            # 閉じる
exec 2>/dev/null     # スクリプトの残りで標準エラー出力を捨てる

ヒアドキュメント

cat <<EOF
home is $HOME, today is $(date +%F)
EOF

cat <<'EOF'
nothing is expanded: $HOME `cmd`
EOF

cat <<-EOF
	leading tabs are removed from each line and from the delimiter line
	EOF
  • 本文はコマンドの次の行から始まり、区切りの単語と同じ行で終わります。1 行に複数のヒアドキュメントがあれば、順に読みます。入力の終わりに来たら、本文はそこで終わります。
  • 区切りの単語のどこかがクォートされていれば('EOF'、"EOF"、\EOF、E"O"F)、本文はそのままです。そうでなければパラメータ展開・コマンド置換・算術展開を行い、\ は $・`・\・改行をエスケープします(ダブルクォートの中と同じ)。
  • <<- は行頭のタブを取り除きます(空白は取り除きません)。
  • 本文はコマンドが始まる前にパイプに書かれます。大きさの上限はありません。

条件式

test コマンド

test expr と [ expr ](組み込みコマンド。/bin/test と /bin/[ にもあります)は次を評価します。

式真になる条件
-e f、-a ff がある
-f fふつうのファイル
-d fディレクトリ
-L f、-h fシンボリックリンク(たどらない)
-p fFIFO
-c fキャラクタデバイス
-s f大きさが 0 より大きい
-r f、-w ff がある(権限は調べません)
-x f実行のビットがどれか立っている
-t fdfd が TTY
-z s / -n ss が空 / 空でない
-v name変数 name が設定されている
ss が空でない
a = b、a == b、a != b文字列が等しい(パターンではない)
a < b、a > b文字列の順(UTF-16)。[ ] の中ではクォートします
a -eq/-ne/-lt/-le/-gt/-ge b整数の比較
a -nt b / a -ot b更新時刻が新しい / 古い(無いファイルはいちばん古いものとして扱う)
a -ef b同じ inode
! e、e1 -a e2、e1 -o e2、( e )否定・かつ・または・グループ(-a は -o より強く結合)

引数なしは偽(1)です。sh: test: <x>: integer expression expected、sh: test: too many arguments、 sh: [: missing ']' はメッセージを出してステータス 2 です。-b・-g・-u・-k・-S・-O・-G・-N には対応していません。

条件コマンド

[[ と ]] の間の単語は、分割もグロブもせずに展開します。使えるもの:

式意味
a == pattern、a = patternシェルのパターンとの照合。パターンのクォートした部分は文字どおり
a != pattern合わない
a =~ regexJavaScript の RegExp での検索(POSIX の ERE ではありません)。クォートしても正規表現は文字どおりになりません。合った部分を BASH_REMATCH に入れます。正しくない正規表現は偽です。
a < b、a > b文字列の順
-eq -ne -lt -gt -le -ge -nt -ottest と同じ
-z -n -e -f -d -x -r -w -s -L -h とオペランドtest と同じ
!、&&、||、( )否定・かつ・または・グループ
word空でなければ真

単項の演算子は表にあるものだけを見分けます。それ以外の -x の単語はただの文字列として扱われます(空ではないので真になります。[[ -p /dev/null ]] は真)。ステータスは 0 か 1 です。

コマンドの実行

コマンドの探し方

単純なコマンドでは、展開と別名の置き換えの後、次の順に探します。

  1. 引数の無い exec: リダイレクトをシェルに適用します。
  2. その名前のシェル関数。
  3. 組み込みコマンド(組み込みコマンド を参照)。
  4. プログラム: 名前に / があれば作業ディレクトリから解決します。無ければ PATH の各ディレクトリで、実行のビットのあるふつうのファイルを探します。PATH に無くても、名前がカーネルのプログラム(ls・sh・npm など)なら、そのプログラムが動きます。

カーネルがプログラムのファイルをどう動かすか(src/kernel/kernel.ts の planExec):

ファイルの先頭動くもの
#!/ov/builtin nameカーネルのプログラム name(node なら Node.js のプロセス)
#!/usr/bin/env [-S] [VAR=val...] interp args(または /bin/env)PATH で探した interp。VAR=val の単語は飛ばします(設定はしません)
#! とインタプリタ node・nodejs・bun・denoNode.js に、インタプリタの引数とファイルを渡したもの
#! とそれ以外のインタプリタそのインタプリタに、ファイルを引数として渡したもの(8 段まで。それを超えると ELOOP)
#! なしで、名前が .js・.cjs・.mjs で終わるNode.js にファイルを渡したもの
#! なしで、それ以外sh file(POSIX の ENOEXEC の扱いと同じ)

どちらの Node.js ランタイム(移植したランタイムか、旧来のランタイムか)を起動するかは、プロセスを起動するたびに /etc/openvessel/node で決まります。Node.js の層 を参照してください。

終了ステータス

ステータス意味
0〜255コマンドの終了ステータス(exit n と return n は n & 255 を使うので、exit 300 は 44)
1リダイレクトの失敗、readonly の変数への代入、${p:?}、set -u の違反
2文法の誤り、test/[/printf/source の使い方の誤り
126プログラムのファイルはあるが実行できない(sh: X: Permission denied)、またはそのほかの起動のエラー
127コマンドが見つからない(sh: X: command not found)、sh file のスクリプトが無い
128 + nシグナル n で終わった: 130 SIGINT、137 SIGKILL、141 SIGPIPE、143 SIGTERM
130シェル自身がコマンドの合間に割り込まれた
141組み込みコマンドが、読み手の無いパイプに書いた(EPIPE)

対話モードでは、プログラムが SIGINT と SIGPIPE 以外のシグナルで終わると、シェルがシグナルの名前(たとえば SIGTERM)を表示します。

環境変数

プログラムが受け取るのは、シェルのエクスポートされた変数(環境から受け継いだもの、export・declare -x で設定したもの、set -a の間に代入したもの)、PWD、そしてコマンドの前の代入です。OLDPWD は cd がエクスポートします。組み込みコマンドと関数は、すべての変数を見られます。

バックグラウンドとサブシェル

シェルは、サブシェル・パイプラインの要素・コマンド置換・バックグラウンドのジョブのために、カーネルのプロセスを作りません。どれも同じシェルのプロセスの中で、シェルの状態のコピーとして動きます(必要なら並行に)。そこから起動したプログラムは別のプロセスです。

  • サブシェル ( ) — 中の exit はサブシェルだけを終わらせます。
  • パイプライン — 各要素はそれぞれのコピーで並行に動きます。ステータスは最後の要素のもの、set -o pipefail のときは失敗したいちばん右の要素のものです。対話でないシェルでは、パイプラインが最初に起動したプログラムがプロセスグループのリーダーになり、ほかのプログラムはそのグループに入ります。パイプラインの要素の中の exit は、その要素だけを終わらせます。
  • バックグラウンド & — コマンドはシェルのコピーで並行に動き、シェルはすぐにステータス 0 で先に進みます。標準入力はリダイレクトしません。$! にはジョブの内部の識別子が入ります。wait(引数なし)はこのシェルのすべてのバックグラウンドのジョブを待って 0 を返します。wait $! は、ジョブがまだ動いていれば、それを待ってそのステータスを返します。終わったジョブは忘れられ、その識別子での wait は 127 を返します。本物の pid を渡した wait <pid> は、そのカーネルのプロセスを待ちます。
npm run build > build.log 2>&1 &
job=$!
echo "building..."
wait "$job"; echo "build exited with $?"

シェルのオプション

set [-+]letters、set -o name / set +o name、または sh のコマンドラインの同じフラグで設定します。

文字-o の名前効果
-eerrexitコマンドが失敗したら終わります。if/elif/while/until の条件、&&/|| のリストの最後以外のコマンド、! のパイプラインには適用しません。関数の中には適用します。
-unounset未設定の変数の展開をエラーにします(ステータス 1、シェルは終わる)。
-xxtrace単純なコマンドごとに、その前に $PS4(既定は + )と、展開した代入と単語を標準エラー出力に書きます。A-Za-z0-9_./=:@%+- 以外の文字を含む単語はシングルクォートで囲みます。
-fnoglobパス名展開をしません。
-aallexport代入した変数をすべてエクスポートします。
—pipefailパイプラインのステータスを、失敗したいちばん右の要素のものにします。

ほかの文字や -o の名前は受け付けて無視します(たとえば set -v、set -o vi、set -n は効果がありません)。引数なしの set は、すべての変数を name='value' の形で、並べ替えて表示します。set -- args と set args (最初の引数が -/+ で始まらない)は位置パラメータを置き換えます。

対話モード

プロンプト

シェルは各コマンドの前に、プロンプトを標準エラー出力に書きます。PS1 が設定されていれば、次のエスケープだけを置き換えます: \w(作業ディレクトリ。$HOME は ~ と表示)、\u(常に user)、\h(ホスト名 openvessel)、 \$($)。PS1 が未設定なら、プロンプトは次のとおりです。

\e[1;32muser@openvessel\e[0m:\e[1;34m~/app\e[0m$ 

つまり、緑の user@openvessel、コロン、青の作業ディレクトリ、$ です。行が文法的に完結していないとき(閉じていないクォート、fi の無い if、行末の |)は、> を出して次の行をつなげます。

入力したコマンドについて: 空の行は無視します。エラーは sh: <message> と表示してステータスを設定します。exit か入力の終わりでシェルは終わります(入力の終わりでは exit と表示し、直前のステータスを返します)。対話シェルは ${p:?} と set -u のエラーでも終わります。

行編集

TTY の上では、シェルは 1 行を読む間、端末をエコーなしの raw モードにし、読み終えたら(前面のプログラムが終わった後も)エコーありの cooked モードに戻します。

キー動作
Enter(CR または LF)行を確定
← / →1 文字移動
↑ / ↓前 / 次の履歴(編集中の行は取っておきます)
Home、Ctrl-A行頭へ(Home: ESC [ H、ESC O H、ESC [ 1 ~)
End、Ctrl-E行末へ(End: ESC [ F、ESC O F、ESC [ 4 ~)
Backspace(DEL または Ctrl-H)カーソルの前の文字を消す
Delete(ESC [ 3 ~)カーソルの位置の文字を消す
Ctrl-D空の行なら入力の終わり。そうでなければカーソルの位置の文字を消す
Ctrl-K行末まで消す
Ctrl-U行頭まで消す
Ctrl-Wカーソルの前の単語を消す(単語は空白で区切る)
Alt-B、Ctrl-←1 単語左へ
Alt-F、Ctrl-→1 単語右へ
Ctrl-L画面を消して行を描き直す
Ctrl-C^C を表示し、行を捨て、ステータスを 130 にして、新しいプロンプトを出す
Tab補完(下を参照)

ほかの制御文字は無視します。TTY が無いとき(パイプから入力した sh -i)は、ただのテキストとして行を読み、プロンプトはやはり標準エラー出力に書きます。

履歴と補完

  • 履歴はシェルが動いている間だけメモリに持ちます(ファイルには保存しません)。直前の項目と同じ行は追加しません。 history で番号付きで表示します。
  • Tab はカーソルの前の単語を補完します。行の最初の単語(/ を含まない)では、組み込みコマンドの名前、関数の名前、PATH のディレクトリにあるすべてのファイルの名前が候補です。それ以外では、作業ディレクトリから見たファイル名です(~/ も分かります)。隠しファイルは、打った部分が . で始まるときだけ候補になります。ディレクトリには / が付きます。
  • 候補が 1 つなら、それを入れて空白を付けます(ディレクトリの後には付けません)。複数なら共通の先頭部分を入れ、何も足せなかったときは候補を行の下に並べます。

シグナル

対話シェルは SIGINT・SIGTSTP・SIGQUIT・SIGWINCH を無視します。起動したプログラムはそれぞれ自分のプロセスグループを持ち、プログラムが動いている間はそのグループが端末の前面のグループになります。そのため Ctrl-C はシェルではなくプログラムに届きます。TTY については プロセス を参照してください。

組み込みコマンド

組み込みコマンドはシェルの中で動きます。echo・printf・test・[・true・false・pwd は、同じ動きのプログラムとして /bin にもあります(xargs・find -exec・env から使えます)。

組み込みコマンドオプションとオペランド説明
:任意ステータス 0
true、false任意ステータス 0 / 1
.、sourcefile [args]file を今のシェルで動かします。/ を含まない名前は PATH(実行可能なファイルだけ)で探し、次に作業ディレクトリで探します。args があれば、その間だけ位置パラメータを置き換えます。オペランドが無いとステータス 2、ファイルが無いと No such file or directory でステータス 1 です。return でファイルの実行を終えます。
alias[name[=value]...]オペランドなしなら alias name='value' の形で一覧を表示します。name だけならその別名を表示します。
unaliasname...-a には対応していません(名前として扱います)。
break、continue[n]外側から数えて n 番目のループを抜ける / 次に進めます。対話でないシェルでループの外で使うと、シェルがステータス 0 で終わります。
builtinname [args]組み込みコマンド name を動かします。無ければステータス 1。
cd[-L|-P] [dir|-]オペランドなしなら $HOME(または /)。- なら $OLDPWD に移り、それを表示します。/ や . で始まらない名前には CDPATH を使います。-L/-P は受け付けて無視します(どちらでもシンボリックリンクはファイルシステムが解決します)。OLDPWD(エクスポート)・PWD・プロセスの作業ディレクトリを設定します。エラーは No such file or directory、Not a directory。
command[-v|-V] name... / name [args]-v/-V: 組み込みコマンドと関数は名前を、プログラムはパスを表示します。見つからないものがあればステータス 1(別名は報告しません)。そうでなければ、シェル関数を飛ばして name を動かします。
declare、typeset[-x] [name[=value]...]関数の中では名前がローカルになります(戻るときに元に戻る)。-x でエクスポートします。ほかのオプションは無視します。オペランドなしでは何も表示しません。
echo[-neE]... [args]echo を参照。
evalargs引数を空白でつなぎ、その結果を動かします。
exec[cmd [args]]コマンドなし: リダイレクトをシェルに適用します。コマンドあり: ふつうのコマンドと同じように動かし(プログラムなら子プロセスになります)、そのステータスでシェルを終えます。本物の execve ではないので、プログラムには新しい pid が付きます。
exit[n]n & 255(既定は直前のステータス)で終わります。サブシェルやパイプラインの要素の中では、その部分だけが終わります。
export[-p] [-n] [name[=value]...]オペランドなしか -p なら、export name='value' の形で並べ替えて表示します。-n は無視します。正しくない名前は sh: export: `x': not a valid identifier。
getoptsoptstring name [args]getopts を参照。
hash任意何もしない。ステータス 0
history—履歴を表示します。番号を 5 桁の右寄せで、空白 2 つ、行の順です。
jobs、times任意何もしない。ステータス 0、出力なし
letexpr...各算術式を計算します。最後の値が 0 以外ならステータス 0。
local[name[=value]...]name を保存し、関数から戻るときに元に戻します。値を付けないと、未設定の変数は空文字列になります。オプションは無視します。関数の外ではただの代入です。
printf[-v var] format [args]printf を参照。
pushd[dir]作業ディレクトリをディレクトリのスタックに積み、cd dir します(オペランドなしなら cd $HOME)。何も表示しません。
popd—スタックのいちばん上に cd し、それを取り除きます。空ならステータス 1。何も表示しません。dirs はありません。
pwd任意シェルの作業ディレクトリを表示します。-L/-P は無視します。
readread を参照
readonly[-p] [name[=value]...]名前を readonly にします(=value があれば先に代入)。オペランドなしでは何も表示しません。
return[n]関数か source したファイルから戻ります。sh -c やスクリプトの最上位では、n でそれを終えます。
setシェルのオプション を参照
shift[n]位置パラメータを n 個(既定は 1)捨てます。n が $# より大きければステータス 1(何も捨てない)。
test、[test コマンド を参照
trapaction signal...動作を記録しますが、実行はしません。ステータス 0。
typename...X is a function、X is aliased to `v'、X is a shell builtin、X is /path のどれかを表示します(この順で調べます)。どれでもなければ sh: type: X: not found でステータス 1。オプションは無視します。
ulimit[-a] [-n] [value]あるのは開けるファイルの数の上限(-n)だけです。これはプロセスが起動できる Node.js のスレッドの数を制限します(スレッド 1 つが記述子 8 つ分、そのほかに基本の 30)。既定は 1024 で、子に受け継がれます。unlimited は 1048576。-a は open files (-n) N を表示します。ほかのオプションは unlimited を表示し、設定しても効果はありません。
umask[mode]マスクを 8 進 4 桁で表示するか、8 進の数で設定します。記号のモードには対応していません(umask u=rwx はマスクを 0 にします)。
unset[-f] [-v] name...変数を、-f の後なら関数を消します。readonly の変数は sh: unset: X: readonly variable でステータス 1。
wait[id...]バックグラウンドとサブシェル を参照。知らない識別子なら 127。

echo

echo [-n] [-e] [-E] [arg...]

先頭の引数のうち、- の後に n・e・E の文字だけが続くものがオプションです(-ne、-en、-nE)。-n は最後の改行を出しません。-e でエスケープを有効にし、-E で無効にします(既定)。-- はふつうの引数として出力します。-e のときのエスケープ: \a \b \e \f \n \r \t \v \\、\0nnn(8 進、3 桁まで)、\xHH。\c で出力を止めます(改行も出しません)。

printf

printf [-v var] format [argument...]
  • 変換: %d %i(整数。切り捨て)、%u(絶対値)、%o、%x、%X、%e %E(JavaScript の指数の形。たとえば 1.234500e+3)、%f %F、%g %G、%c(最初の 1 文字)、%s、%b(バックスラッシュのエスケープを解釈した引数)、%q(シェル用にクォート)、%%。
  • フラグ: -(左寄せ)、0(数を 0 で埋める)、+ と空白(%d/%i の符号)、#(%x にだけ 0x を付ける)。幅と精度は数か *。精度は %s/%b を切り詰めます。
  • 書式の中のエスケープ: \a \b \e \f \n \r \t \v \\ \" \'、\NNN と \0NNN(8 進)、\xHH、\c(出力を止める)。
  • 数の引数: 'c や "c は文字コードになります。数でない文字列は parseFloat で読み、それでもだめなら 0 です。エラーにはなりません。
  • 引数が残っている間、書式を繰り返し使います。足りない引数は空 / 0 です。
  • -v var は、出力する代わりに結果を var に入れます。書式が無いと usage: printf format [arguments] でステータス 2。

read

read [-r] [-p prompt] [-d delim] [-n count] [-N count] [-s] [-t x] [-u x] [-a x] [name...]
  • fd 0 から区切り(既定は改行)まで 1 バイトずつ読み、その後 UTF-8 として解読します。
  • -r が無ければ、\ は次の文字をエスケープし、\ と改行の組は行を続けます。
  • -p はプロンプトを標準エラー出力に書きます。-d '' は NUL を区切りにします。-n/-N は count バイトで止めます。-s は受け付けますが効果はありません。-t・-u・-a は引数を 1 つ取り、無視します。
  • 名前なし: 行全体を、前後を削らずに REPLY に入れます。名前 1 つ: 前後の IFS の空白を除いた行を入れます。名前が複数: 行を IFS の文字で分け、最後の名前に残り全部を入れます。
  • 何も読めないまま入力が終わるとステータス 1 です(改行の無い最後の行は 0 を返します)。

getopts

while getopts "ab:" opt; do
  case $opt in
    a) echo "flag a" ;;
    b) echo "b=$OPTARG" ;;
    ?) exit 2 ;;
  esac
done
shift $((OPTIND - 1))

args(既定は位置パラメータ)を OPTIND(既定は 1)から解析します。optstring で後ろに : の付いたオプションは引数(続けて書いたものか次の単語)を取り、OPTARG に入れます。引数が無ければ OPTARG は空です。知らないオプションでは、何も表示せずに name を ? にします。最初のオプションでない引数、-、--(これは読み飛ばす)で、name を ? にしてステータス 1 を返します。

プログラム

以下のプログラムは、特に書いていなければ /bin にあります。コマンドごとに断りが無ければ、オプションは src/kernel/coreutils.ts の小さな getopt で解析します。

  • 短いオプションはまとめられます(-la)。値を取るオプションは、続けて(-n5)も次の引数として(-n 5)も書けます。
  • オプションはコマンドラインのどこにあっても、オペランドの後でも認識されます。-- で終わります。
  • -<数字> の形の引数(たとえば -1、-0)はオプションではなくオペランドです。
  • 知らないオプションは黙って受け付け、無視します。
  • 長いオプションは、書いてあるものだけ対応します。--name=value で値を設定します。
  • 読めないファイルのオペランドは <cmd>: <file>: <reason> と表示して次に進み、終了ステータスは 1 になります(ls・grep・diff は 2)。
  • ファイルを読むコマンドでは、オペランドの - は標準入力です。

コマンド一覧

コマンドオプション説明 / GNU との違い
base64-d、-w…76 桁の行でエンコードします。-w がどれかあれば折り返しません(値は読みません)。ファイルのオペランドは最後のものだけを使います。-d は空白を無視します。
basename-a、-s SUFbasename NAME [SUFFIX]。-a/-s ではすべてのオペランドを処理します。
cat-nほかのオプションは無視します。標準入力は届いた順に流します。
chmod-Rchmod を参照。
chown—何もせず、ステータス 0。
clear—ESC[H ESC[2J ESC[3J を書きます。
cp-r -R -a -n -P -d -f、--recursive、--forcecp, mv, rm, ln を参照。
cut-d C、-f LIST、-c LIST、-b LISTcut を参照。
date-u、--utc、-d STR、--date=STR、+FORMATdate を参照。
df—決まった行を表示します: vfs 4194304 0 4194304 0% /。
diff-qdiff を参照。
dirname—オペランドごとに 1 行。
du-s、-h、-a、-d N、--max-depth=Ndu を参照。
echo-n -e -E組み込みコマンドと同じ。
egrep、fgrepgrep と同じgrep -E / grep -F。
env-i、-、-u NAME、-S STRINGenv を参照。/usr/bin/env にもあります。
false—ステータス 1。
file—file を参照。
findfind を参照
free—決まった値(total 4194304、used 0)を表示します。
grepgrep を参照
head-n N、-N、-c Nhead と tail を参照。
hostname—openvessel。変えられません。
id-u、-g、-un、-nuuid=1000(user) gid=1000(user) groups=1000(user)。-u/-g は 1000、-un は user。
kill-SIG、-N、-lps と kill を参照。
less、morecat と同じページ送りはなく、cat とまったく同じ動きです。
ln-s、-f、-ncp, mv, rm, ln を参照。
lsls を参照
md5sum、sha1sum、sha256sum、sha512sum—チェックサム を参照。
mkdir-p、--parents、-m MODE-m で 8 進のモードを指定します。無ければ新しいディレクトリは 0755 になります。オペランドが無いとステータス 1。
mkfifo-m MODE既定のモードは 0666 & ~umask。
mktemp-d、-t、-p DIRmktemp を参照。
mv-ncp, mv, rm, ln を参照。
nice-n Nコマンドを動かします。nice の値は無視します。
nl—空でない行に番号を付けます(%6d<TAB>)。空の行は空のまま出力します。オペランドはファイルです。
nohup—コマンドをそのまま動かします(nohup.out は作らず、シグナルも変えません)。
nproc—navigator.hardwareConcurrency、無ければ 4。
printf-v VAR組み込みコマンドと同じ。
ps—ps と kill を参照。
pwd—プロセスの作業ディレクトリを表示します。
readlink-f、-e、-mオプションなし: リンク先を表示します。シンボリックリンクでなければメッセージなしでステータス 1。どれかのオプションあり: 正規のパスを表示します。無ければメッセージなしでステータス 1。
realpath-m正規のパス。-m では、無いパスでも文字の上で解決したパスを表示します。
rev—各行を(コードポイント単位で)逆にします。
rm-r -R --recursive -f --force -dcp, mv, rm, ln を参照。
rmdir-p-p では、オペランドに書いた親ディレクトリも消します。
sedsed を参照
seqseq を参照
sleep—sleep を参照。
sortsort を参照
stat-L、-c FMTstat を参照。
sttysize、raw、-raw、sane、cooked、echo、-echo標準入力が TTY でなければステータス 1。size は rows cols を表示します。ほかの設定は無視します。
sync—何もせず、ステータス 0。
tail-n N、-n +N、-N、-c N、-fhead と tail を参照。
tee-a標準入力を標準出力と各ファイル(切り詰める。-a なら追記)に写します。
test、[—組み込みコマンドと同じ。
time—time と timeout を参照。
timeout—time と timeout を参照。
touch-c。-d X・-t X・-r X は受け付けるだけ無いファイルを作り、あるファイルのアクセス時刻と更新時刻を今にします。-d/-t/-r の値は無視します(常に今)。-c: 作らない。
tr-d、-s、-ctr を参照。
true—ステータス 0。
truncate-s N各ファイルを N バイト(既定は 0)にします。無ければ作ります。受け付けるのはただのバイト数だけです(K/M の接尾辞や +/- はありません)。
tty—標準入力が TTY なら /dev/pts/N、そうでなければ not a tty でステータス 1。
uname-a -s -n -r -v -m -p -oLinux、openvessel、6.1.0-openvessel、#1 SMP、x86_64(-m と -p)、GNU/Linux。オプションなしは -s。-a は s n r v m o の順に表示します。
uniq-c、-d、-u、-iuniq を参照。
wc-l、-w、-c、-mwc を参照。
which-aPATH で見つけたパスを表示します。-a は実行可能なものをすべて表示します。見つからない名前があればステータス 1。
whoami—user。
xargs-n N、-I STR、-d DELIM、-r、-P Nxargs を参照。
yes—引数を空白でつないだもの(既定は y)を、止められるかパイプが閉じるまで繰り返します。
curl、wgetcurl と wget を参照/usr/bin にあります。
node、nodejs—/usr/bin にあります。Node.js の層 を参照。
npm、npx、pnpm、pnpx、yarn、bun、bunx、corepack—/usr/bin にあります。パッケージマネージャ を参照。

awk・od・xxd・tar・gzip・unzip・git・python・make・エディタはありません。動かすと command not found(ステータス 127)になります。

ls

ls [-a] [-A] [-l] [-h] [-R] [-t] [-r] [-d] [-1] [--no-color] [file...]
オプション長い形効果
-a--all. で始まる名前と、.・.. も表示
-A--almost-all. で始まる名前も表示(. と .. は除く)
-l--long長い形式
-h--human長い形式で大きさを K・M・G・T(1024 の累乗。10 未満は小数 1 桁)で表示
-R--recursiveサブディレクトリも再帰的に表示
-t更新時刻の新しい順
-r順を逆に
-dディレクトリの中身ではなく、ディレクトリ自身を表示
-11 行に 1 つ。ほかの文字とまとめたとき(-1a)だけ効きます。-1 単独はファイル名として扱われます
--no-color色を付けない
  • 名前は localeCompare で並べます。標準出力が TTY でなければ 1 行に 1 つ、TTY なら端末の幅に合わせた段組み(上から下へ埋める)で、色を付けます。ディレクトリは太字の青、シンボリックリンクは太字のシアン、実行可能なファイルは太字の緑です。
  • 長い形式: mode links user user size date name。リンクには -> target が付きます。所有者とグループは常に user です。日付は今から 180 日以内なら Mon dd HH:MM、それ以外は Mon dd YYYY。各一覧の前に total の行(ファイルごとに KiB に切り上げた大きさの合計)が付きます。
  • 無いオペランドは ls: cannot access 'x': No such file or directory で、ステータス 2。

cat

cat [-n] [file...] はファイルをつないで標準出力に書きます。オペランドなしか - では、標準入力をかたまりごとに流します(パイプの向こうの読み手には、届いたそばからデータが見えます)。-n はすべての行に番号を付けます(%6d<TAB>)。

head と tail

head [-n N | -N | -n -N] [-c N] [file...]
tail [-n N | -N | -n +N] [-c N] [-f] [file...]
  • head: 最初の N 行(既定は 10)か N バイト。-n -N は最後の N 行を除いたすべて。数が負でなければ、標準入力からは必要な行を読んだ時点で読むのをやめるので、yes | head -1 は終わります。複数のファイルでは、それぞれの前に ==> name <== が付きます。
  • tail: 最後の N 行(既定は 10)、+N なら N 行目から、-c なら最後の N バイト(-c +N には対応していません。最後の N バイトとして扱います)。tail はまず入力をすべて読みます。複数のファイルでも見出しは付きません。
  • tail -f file(ファイル 1 つだけ): 表示した後、250 ms ごとにファイルを調べ、増えた分を止められるまで表示します。ファイルが小さくなったら、新しい末尾から続けます。

wc

wc [-l] [-w] [-c] [-m] [file...]。オプションなしでは行数・単語数・バイト数です。-m は 3 列目をバイトではなく文字(コードポイント)の数にします。単語は空白でない文字の並びです。列はいちばん長い数に合わせて右寄せします。標準入力だけを読んだときは、詰めずに数を表示します。入力が 2 つ以上なら total の行が付きます。

grep

grep [options] pattern [file...]
grep [options] -e pattern [file...]
egrep ...    fgrep ...
オプション長い形効果
-i--ignore-case大文字と小文字を区別しない
-v--invert-match合わない行を選ぶ
-n--line-number行番号を付ける
-c--countファイルごとに選んだ行の数を表示
-l--files-with-matches合ったファイルの名前だけを表示
-q--quiet、--silent出力なし。ステータスだけ
-w--word-regexp単語全体に合わせる(\b…\b)
-x行全体に合わせる
-o--only-matching合った部分を 1 行ずつ表示
-E--extended-regexp拡張の書き方
-F--fixed-stringsただの文字列
-r、-R--recursiveディレクトリを再帰的に探す(既定は .)
-hファイル名を付けない
-aバイナリのファイルもテキストとして扱う
-e PATパターン(1 つだけ。最後の -e が使われます)
-m Nファイルごとに、選んだ行が N 行になったら止める
--include=GLOB-r のとき、ベース名が合うファイルだけ
-A N、-B N、-C N--exclude=…、--color…受け付けて無視(前後の行は出ません)
  • パターンは JavaScript の RegExp に変換します。基本の書き方(既定)では \( \) \{ \} \| \+ \? が演算子で、\ の無いものは文字そのものです。-E ではパターンをそのまま渡します。それ以外は JavaScript の書き方に従います。\d・\w・\s・\b は使えますが、[[:digit:]] のような POSIX の括弧の文字クラスは変換しません。正しくないパターンは invalid regular expression でステータス 2。
  • ファイルが 2 つ以上か -r のとき、ファイル名を付けます。-r は、ドットファイルや node_modules も含めてすべてのファイルとディレクトリをたどります。. の下の名前は ./ を付けずに表示します。
  • NUL のバイトを含むファイルは Binary file NAME matches と表示します(-a が無ければ)。
  • -o はすべての一致を表示します。空の文字列に合いうるパターンでは、空の行が出ます。
  • 終了ステータス: 選んだ行があれば 0、無ければ 1、読めないファイルがあって何も選ばなかったときは 2。

sed

sed [-n] [-E|-r] [-i[SUFFIX]] [-e script]... [-f file] [script] [file...]
オプション効果
-n、--quietパターンスペースを自動では表示しない
-e scriptスクリプトを足す(複数あれば改行でつなぐ)
-f fileファイルからスクリプトを読む
-E、-r拡張の正規表現
-i[SUFFIX]ファイルをその場で書き換える。接尾辞があれば、控えを file+SUFFIX に残す(接尾辞は続けて書く: -i.bak)

-e/-f が無ければ、オプションでない最初の引数がスクリプト、残りがファイルです。オプションは別々に書きます(-ne ではなく -n -e …)。まとめたものや知らないオプションはファイル名として扱われます。-i が無ければ、すべての入力をつないで 1 つの流れとして処理します。

アドレス: 行番号 N、$(最後の行)、/regex/(または別の区切りで \cregexc)。どれも後ろに I を付けると大文字と小文字を区別しない正規表現になります。addr1,addr2 は範囲を選びます(addr2 が今の行以下の行番号なら 1 行だけ)。アドレスの後の ! は否定です。

コマンド効果
s/re/repl/flags置換。区切りは任意。フラグ: g(すべて)、N(N 番目の一致だけ。1 桁)、p(置換したら表示)、i/I(大文字と小文字を区別しない)。repl の中では & = 一致した部分、\1〜\9 = グループ、\& = & そのもの、\n = 改行。
y/abc/xyz/文字の置き換え
d行を消し、次の周期へ
p行を表示
=行番号を表示
q(-n でなければ)表示して終わる
a text、a\ + 改行 + text行の後に文字列を足す
i text行の前に文字列を入れる
c text行を文字列に置き換える

コマンドは ; か改行で区切ります。正規表現は grep と同じ BRE→JavaScript の変換を使います。対応していないもの: { } のブロック(中かっこは無視されるので、中のコマンドはすべての行に適用されます)、n N P D h H g G x b t T r w l、 0,/re/、addr,+N、first~step。改行の無い最後の行は、改行なしで出力します。

sed -i 's/"dev": ".*"/"dev": "astro dev"/' package.json
sed -n '/^## /p' README.md
sed -E 's/([0-9]+)\.([0-9]+)/\2.\1/g' versions.txt

find

find [path...] [expression]

パスは、先頭から続く - で始まらず ! でも ( でもない引数です。既定は .。項目は並べ替えた順に深さ優先でたどり、シンボリックリンクはたどりません。結果は、渡したパスに相対の名前を足した形で表示します(./src/a.js)。

式意味
-name PAT、-iname PATベース名がシェルのパターンに合う(大文字と小文字を区別しない)
-path PAT、-wholename PAT表示するパス全体が合う(* は / にも合う)
-type f|d|lふつうのファイル、ディレクトリ、シンボリックリンク(ほかの文字は何にも合わない)
-maxdepth N、-mindepth N全体の深さの制限(どこに書いても同じ)
-empty空のファイルかディレクトリ
-newer FILEFILE より後に更新された
-size [+-]N[ckMG]大きさ。単位は 512 バイト(既定)、c バイト、k KiB、M MiB、G GiB。+ は大きい、- は小さい、無ければ切り上げて等しい
-true、-false
-print、-print0パスに改行 / NUL を付けて表示
-pruneこのディレクトリの中に入らない
-delete消す(ディレクトリは中身ごと)
-exec CMD {} ;、-exec CMD {} +{} をパスに置き換えて CMD を動かす。+ でもパス 1 つにつき 1 回動かす
!、-not、( )、-a、-and、-o、-or演算子。並べて書くと -a
  • -print・-print0・-delete・-exec が無ければ、式が真になった項目をすべて表示します。
  • -delete と -exec は後回しになり、すべてたどり終えてから動きます(消すのは深いものから)。{} が置き換わるのは、引数全体が {} のときだけです。-exec のコマンドが 1 つでも失敗すればステータス 1。
  • 知らない述語は find: unknown predicate '-x' でステータス 1。無い開始パスは find: 'path': No such file or directory でステータス 1。
  • ディレクトリへの -empty は、開始パスが絶対パスのときだけ動きます。相対の開始パスでは、ディレクトリに来たところでエラーになって止まります。

sort

sort [-n] [-h] [-f] [-r] [-u] [-t SEP] [-k N] [-o FILE] [file...]
オプション効果
-n、-hキーを parseFloat した数で比べる(数でなければ 0。-h も K/M の接尾辞は分かりません)
-f大文字と小文字を区別しない
-r逆順
-u隣り合う同じ行を 1 つにする(行全体で比べる)
-t SEPフィールドの区切り(既定は、行の前後を削った後の空白の並び)
-k NN 番目のフィールドから行末までをキーにする(-k2,2 や -k2n のような修飾は -k2 として読みます)
-o FILEFILE に書く

比較は UTF-16 のコード単位の順です(ロケールによる照合はしません)。並べ替えは安定です。GNU のほかのオプション(-V・-c・-s・-z・-b・-M)は無視します。

uniq

uniq [-c] [-d] [-u] [-i] [file] は、隣り合う同じ行を 1 つにします。-c は数を前に付け(%7d )、-d は重複した行だけ、-u は重複しない行だけを表示し、-i は大文字と小文字を区別しません。読むのは最初のオペランドだけで、出力ファイルのオペランドは無視します。

cut

cut -f LIST [-d C] [file...] はフィールドを選びます(既定の区切りは TAB)。-c LIST と -b LIST は文字を選びます(どちらもバイトではなく文字で数えます)。LIST はコンマ区切りの N・N-M・N-・-M です。-f では、区切りの無い行はそのまま表示し(-s はありません)、出力の区切りは入力の区切りと同じです。

tr

tr [-d] [-s] [-c] SET1 [SET2] は標準入力だけを読みます。集合には範囲(a-z)、クラス [:lower:] [:upper:] [:digit:] [:space:] [:alpha:] [:alnum:]、\n \t \r \\ を使えます。SET2 が短ければ、最後の文字を繰り返します。-d は SET1 の文字を消し、-s は置き換えた文字の繰り返しを 1 つにし、-c は SET1 の補集合を使います。 -c と SET2 を一緒に使うと、すべての文字(SET1 の文字も)が置き換わるので、-c が確実に使えるのは -d と一緒のときです。

echo "Hello" | tr a-z A-Z        # HELLO
tr -d '\r' < dos.txt > unix.txt
tr -cd '[:alnum:]\n' < in.txt

xargs

xargs [-n N] [-I STR] [-d DELIM] [-r] [-P N] [--] [command [arg...]]
  • 標準入力をすべて読み、空白で(クォートは処理しません)、-d があれば DELIM(ただの文字列)で項目に分けて、 command(既定は echo)に項目を足して動かします。一度に全部か、-n なら N 個ずつです。
  • -I STR: 項目ごとに 1 回コマンドを動かし、各引数の中の STR をすべて置き換えます。項目はこのときも改行ではなく空白で分けます。
  • -r: 項目が無ければコマンドを動かしません(無ければ、項目なしで 1 回動かします)。
  • -P は受け付けますが、コマンドは常に 1 つずつ順に動きます。
  • NUL 区切りの入力: 0 のフラグは、ほかの文字とまとめて書く必要があります(-0r)。-0 単独はコマンド名として扱われます。
  • 1 回でも 0 以外で終われば、ステータス 123。
  • オプションはどこにあっても認識されるので、command に渡すつもりのオプションも、-- をコマンドの前に置かない限り xargs が取ってしまいます。
find . -name '*.log' -print0 | xargs -0r rm -f      # ここでは -f を xargs が読む
find . -name '*.log' -print0 | xargs -0r -- rm -f   # -f が rm に届く

cp, mv, rm, ln

  • cp [-r|-R|-a] [-n] [-P|-d] SRC... DEST: 元が複数なら DEST はディレクトリでなければなりません。-r/-R/-a なしでディレクトリを渡すと cp: -r not specified; omitting directory 'x'。-n は、あるファイルを飛ばします。元のファイルとして渡したシンボリックリンクは、-a・-P・-d が無ければリンク先のファイルとしてコピーします。コピーするディレクトリの中のリンクはリンクのままです。-a は「リンクをたどらない再帰」の意味で、ほかの -a の意味(所有者・時刻)は cp 自身では実装していません。
  • mv [-n] SRC... DEST: 名前を変えます。ディレクトリでない既存の対象は置き換えます。-f・-i・-v は無視します。
  • rm [-r|-R] [-f] [-d] FILE...: -r はディレクトリを中身ごと、-d は空のディレクトリを消します。-f は無いファイルとオペランドの不足を無視します。/ は消しません(it is dangerous to operate recursively on '/')。確認はしません(-i は無視)。
  • ln [-s] [-f] [-n] TARGET [LINK]: 既定はハードリンク、-s でシンボリックリンク。LINK が無ければ、作業ディレクトリに対象のベース名で作ります。LINK がディレクトリなら(-n が無ければ)その中に作ります。-f は既存の LINK を先に消します。使うのは最初の 2 つのオペランドだけです。

chmod

chmod [-R] MODE FILE...。MODE は 8 進(755)か、[ugoa]*[+-=][rwxXst]* のコンマ区切りの並びです(文字が無ければ a)。+ と - は、指定したクラスのビットを足し / 消します。= は権限のモード全体を指定したビットにします(chmod u=rw f は 600 になり、「u を rw にしてほかはそのまま」ではありません)。X は、ディレクトリか、すでに実行のビットのあるファイルにだけ実行のビットを立てます。s と t は受け付けますが、何も立てません。 -r・-w・-x・-X で始まる引数は解析の前に取り除かれるので、-x のようなモードは効果がありません(a-x と書きます)。-R は再帰します。

stat

stat [-L] [-c FORMAT] FILE...。-L が無ければリンクはたどりません。既定の出力は次のとおりです。

  File: f.txt
  Size: 18	Blocks: 1	IO Block: 4096	regular file
Device: 2049	Inode: 139	Links: 1
Access: (0644/-rw-r--r--)  Uid: ( 1000/ user)   Gid: ( 1000/ user)
Modify: 2026-10-26T06:11:32.702Z

-c FORMAT の指定子: %n 名前、%s 大きさ、%a 8 進のモード、%A モードの文字列、%F ファイルの種類(regular file・directory・symbolic link)、%i inode、%Y 更新時刻(秒)。ほかの %x は ? になります。最後に改行が付きます。-f は引数を 1 つ取り、それ以外は無視します。

du

du [-s] [-h] [-a] [-d N|--max-depth=N] [path...]。ディレクトリは 4096 バイトと中身の合計、ファイルは大きさで数えます。値は KiB に切り上げたもの、-h なら人が読みやすい形です。ディレクトリは中身の後に表示します。-a はファイルも表示し、-s はオペランドだけを表示します。エラーは表示しますが、ステータスは常に 0 です。

date

date [-u|--utc] [-d STRING|--date=STRING] [+FORMAT]

-d @SECONDS は Unix 時刻を使います。それ以外の文字列は JavaScript の Date で解析します。書式が無ければ、出力は JavaScript の Date.prototype.toString()(-u なら toUTCString())で、ブラウザのタイムゾーンです。書式の指定子:

指定子値
%Y %y %m %d年、2 桁の年、月、日
%H %M %S時、分、秒
%sUnix 秒
%Nナノ秒(精度はミリ秒)
%F %T%Y-%m-%d、%H:%M:%S
%b %aJan…Dec、Sun…Sat
%Z-u なら UTC、それ以外は常に JST
%%%

ほかの指定子はそのまま出力します。時計の設定には対応していません。

diff

diff [-q] FILE1 FILE2(どちらも - で標準入力にできます)。同じなら出力なしでステータス 0。違えばステータス 1 で、-q なら Files A and B differ、そうでなければ --- A / +++ B の見出しの後に、両方のファイルのすべての行を、空白(共通)・+・- を付けて出力します。@@ の区切り(hunk)は無く、前後の行数の制限もありません。変わったところでは、足した行を消した行より先に並べます。行数の積が 40 000 000 を超えると、Files A and B differ だけを出力します。ほかのオプション(-u・-r など)は無視し、ディレクトリは比べません。エラーはステータス 2。

file

file NAME... は directory、symbolic link to TARGET、empty、data(最初の 512 バイトに NUL がある)、 ASCII text(それ以外。UTF-8 も含む)のどれかを表示します。エラーは標準出力に NAME: cannot open (reason) と出し、ステータスは常に 0 です。

mktemp

mktemp [-d] [-t] [-p DIR] [TEMPLATE] は、ファイル(モード 0600、排他的に作成)か、-d ならディレクトリ(モード 0700)を作り、その絶対パスを表示します。TEMPLATE(既定は tmp.XXXXXXXXXX)の末尾の X は、ランダムな [a-z0-9] の文字に置き換えます。ディレクトリは、-p なら DIR、-t かテンプレートが無ければ $TMPDIR(/tmp)、それ以外は作業ディレクトリです。

seq

seq LAST、seq FIRST LAST、seq FIRST INCREMENT LAST。数は負でも小数でもよく、JavaScript の数の書き方で表示します。増分が 0 なら、出力なしでステータス 1。-s は動きません。-s, は無視され、-s , は出力が空になります。

sleep

sleep N[s|m|h|d]... は引数の合計だけ眠ります(小数も可。既定の単位は秒。正しくない引数は無視)。シグナルで眠りは終わります。

チェックサム

md5sum・sha1sum・sha256sum・sha512sum は、入力ごとに HEX NAME を表示します(標準入力は -)。SHA は Web Crypto、MD5 は @noble/hashes で計算します。検査のモード(-c)はありません。エラーは結果より先に表示します。

ps と kill

ps はオプションを取らず、動いているすべてのプロセス(カーネルのプログラムと Node.js のプロセス)について PID PPID CMD を表示します。

kill [-SIGNAL | -NUMBER] PID...: 既定のシグナルは SIGTERM です。-9・-KILL・-SIGKILL は同じです。負の pid はプロセスグループに送ります(kill -TERM -123)。kill -l は HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM を表示します。-s SIGNAL には対応していません。失敗すると kill: (PID) - No such process でステータス 1。シグナルそのものは プロセス で説明しています。

env

env [-i | -] [-u NAME]... [-S STRING] [NAME=VALUE]... [command [arg...]]

コマンドが無ければ、できあがった環境変数を NAME=VALUE の行で表示します。-i/- は空の環境から始めます(NAME=VALUE のオペランドだけが残ります)。-u は変数を消します。-S STRING は残りを空白で分けます(#!/usr/bin/env -S のため)。起動できないコマンドは env: 'cmd': No such file or directory でステータス 127。

time と timeout

time command [arg...] はプログラムを動かし(シェル関数や複合コマンドは動かせません。キーワードではなくプログラムです)、その後、空の行に続けて real\tMmS.SSSs を標準エラー出力に書きます。user と sys の行はありません。ステータスはコマンドのものです。

timeout DURATION command [arg...] はコマンドを動かし、DURATION 秒後に SIGTERM を送ります(parseFloat で読むので、m などの接尾辞は無視されます。1m は 1 秒です)。時間切れならステータス 124、そうでなければコマンドのステータスです。timeout は - で始まる引数をすべて取り除きます。コマンド自身のオプションも取り除かれるので、-s・-k・--preserve-status は使えず、timeout 5 sh -c '...' では -c が無くなります。そのようなコマンドはスクリプトのファイルにまとめてください。

curl と wget

curl と wget は /usr/bin にあります。要求はカーネルの通信の関所とランタイムの fetch のプロキシを通るので、ネットワークポリシー が適用され、相手のサーバーはブラウザからのクロスオリジンの要求を許していなければなりません。ブラウザが送れない要求は、ステータス 7 で失敗します。

curl [options] URL
オプション効果
-X M、--request MHTTP のメソッド
-H "K: V"、--header要求のヘッダーを足す
-d S、--data、--data-raw、--data-binary要求の本文(文字列そのもの。@file は読みません)。GET を POST に変えます。
-o FILE、--output本文を FILE に書く
-O、--remote-name本文を URL のベース名(空なら index.html)に書く
-I、--headHEAD の要求。状態の行とヘッダーだけを表示
-i、--include本文の前に状態の行とヘッダーを表示
-f、--failHTTP の状態が 400 以上なら、curl: (22) The requested URL returned error: N でステータス 22
-s、--silent、-S、-L、--location、-k、--insecure、--compressed受け付けるだけで効果はありません(進み具合の表示はなく、リダイレクトは常にたどります)
  • 短いフラグをまとめられるのは s S L f k の組み合わせだけです(-fsSL)。
  • スキームの無い URL には http:// を付けます。使う URL は 1 つだけ(オプションでない最後の引数)です。
  • -i/-I は HTTP/1.1 <code> <text> と、プロキシの応答のヘッダーのうち内部用の x-ov-* を除いたものを表示します。つまり content-type と cache-control: no-store で、サーバーのすべてのヘッダーではありません。理由の文字列は空のことがあります。
  • ブラウザがスクリプトに設定を許さない要求のヘッダー(Cookie・Origin・Referer・Host・Accept-Encoding・ Sec-*・Proxy-* など)は、プロキシが落とします。Cookie は送りません。
  • URL が無ければ curl: no URL specified! でステータス 2。通信のエラーや拒否された要求は curl: (7) <message> でステータス 7。

wget [-O FILE] URL は FILE(-O - なら標準出力)に、既定では URL のベース名か index.html にダウンロードします。中では curl -fsSL を動かすので、HTTP のエラーはステータス 22 です。ほかのオプションは無視します。ただし、- で始まらない最初の引数(-O の値を除く)が URL として扱われる点に注意してください。

この文書の元は、リポジトリの docs/reference/shell.ja.md です(英語版は shell.md)。