tar(1) はなぜオプション引数にハイフンが不要なのか?

TL;DR

tar(1)の引数はオプションではなくkeyだから

歴史

tar(1)は Unix v7 (1979) で導入されたが、その前身は Unix v4 (1973) の tp(1)、更にこれは Unix v1 (1971) の tap(1) に遡る。

80年代に入りSystemV v.s. BSDといったことが起きたりし、PWB/UNIX (Programmer's WorkBench)で導入された cpio(1)tar(1)、どちらがUnixの標準アーカイバか争われた結果、IEEE Std. 1003.1-2001 (POSIX.1-2001) を以って tar(1)は規格から削除され、代わりに IEEE Std 1003.2-1992 で導入された折衷案のpax(1)が標準となった。よって、現在実装中立なtar(1)の仕様書がそもそも存在しない。最後の中立規格は1997―1998年策定のSUSv2 (UNIX V98) の仕様となる。

ちなみに、Unix v4 は C 言語が導入されカーネルやコマンドが全面的にアセンブリから C に書き直された歴史的なリリースで、PWB/UNIXは Unix v6 を fork して世界最大のユーザーを抱えるサイトとしてベル研究所内部で運用された Unix バージョン。様々な開発者向けのコマンドが開発されたようだ。たとえばバージョン管理システムの祖先、sccs(1)はPWB/UNIXが初出。(なぜかこちらは、DEVELOPMENT コマンドという註釈つきで現行の IEEE Std. 1003.1-2017 な POSIX に残っている。)

仕様

tap(1) の頃から一貫して、


NAME            tap  --  manipulate DECtape

SYNOPSIS        tap  [ key ] [ name ... ]

DESCRIPTION     tap saves and restores selected portions of the
                file system hierarchy on DECtape. 
[..]
                The function portion of the key is specified by
                one of the following letters:
                   r  The indicated files and directories,
[..]

と説明がされている。SUSv2 のtar(1)では、


 NAME
     tar - file archiver (LEGACY)
 SYNOPSIS

     tar key [file...]

 DESCRIPTION
     The tar utility processes archives of files. Its actions are controlled by the key operand.
 OPTIONS
     None.
 OPERANDS
     The following operands are supported:
     
     key
         The key operand consists of a function letter followed immediately by zero or more modifying letters. The function letter is one of the following:
[..]

となっており、tap(1)の頃からあまり大差はない。

tar(1)におけるkeyとは。なぜサブコマンドではないのか。

tar(1)における key というのは r、x、t、u、c いずれかの機能(つまり、Update、eXtract、Createなど)をまず指定し、続けて空白を入れずにfオプションだったりvオプションだったり、挙動を指定するオプション引数を続ける、ひとかたまりの文字列ということになっている

現代ならこれは

tar extract -v -f example.tar

のようにサブコマンド指定とそれに対してのオプション、というように引数を設計するだろう。また、PWB/UNIXでは既に導入されていたsccs(1)では既にサブコマンドのあるコマンドが導入されている。

しかしながら、全面的にPDP-11のアセンブリで記述されていた Unix v1 の頃からある tar(1)(の前身)では高度な引数解析を持つ実装が用意されなかったのだろう。それがそのまま、現代にも引き継がれてしまっている。

結果として、tar(1)はサブコマンドのつもりで一文字目に function letter を指定し二文字目以降にオプション文字を並べる、ひとつの文字列として key string を受け入れる、という他の Unix コマンドとやや毛色の違うオプション仕様になってしまっている。

現代のtar実装

FreeBSD

FreeBSD RELEASE 14.1 の man page https://man.freebsd.org/cgi/man.cgi?tar(1) を見るに、

NAME
       tar -- manipulate tape archives

SYNOPSIS
       tar [bundled-flags <args>] [<file> | <pattern> ...]
       tar {-c}	[options] [files | directories]
       tar {-r | -u} -f	archive-file [options] [files |	directories]
       tar {-t | -x} [options] [patterns]

function letter であってもハイフンつきのオプションとして、最初のオプションに指定することで挙動を指定するのが正則で、key string での指定は bundled-flags という名前で歴史的互換性のための指定方法ということになっている。

このあたり、function それぞれで指定できるオプションの種類などがmanを一瞥しただけで理解できる書き方になっていてとてもわかりやすくて良い。

なお、-c指定のときに-fを指定しなかった場合、/dev/sa0 (FreeBSD)/dev/st0 (Linux)がデフォルト指定されることになっており、現代でも今なお、TapeARchiveというコマンドの由来の残り香がある。

GNU

このあたり、GNU Tar 1.35 (2023/08/22) でも大差はない。https://www.gnu.org/software/tar/manual/html_node/basic-tar-options.html#basic-tar-options

つまり、--create --verbose-c -vなどのように、ハイフンつきfunction letterを最初にもってきてその後に個別オプションを指定する、という書き方がまず説明されており、key string はあくまで歴史的互換性で残されているもののようだ。

ただし、--file, -fについての扱いは FreeBSD とはやや異なる。指定をしなかったときのデフォルト指定はTAPE環境変数の指定に従うことになっており、この環境変数が指定されていなかった場合の挙動はコンパイル時に設定されているデフォルト設定に従うことになっている。https://www.gnu.org/software/tar/manual/html_node/file-tutorial.html#file-tutorial

たとえば、tar --show-defaultsの実行結果として以下の出力だったとしたとき

$ tar --show-defaults
--format=gnu -f- -b20 --quoting-style=escape
--rmt-command=/etc/rmt --rsh-command=/usr/bin/rsh

デフォルトで -f-が指定されているので、特に何も指定していなくても勝手に標準入力や標準出力を使ってくれる、ということになる。このあたり、完全に各 distribution で作成されているパッケージのビルドオプション次第ではあるものの、大抵の環境では -f- が指定されていることが期待できそうな気がする。

追記

Unix v1 のその先の源流をさらに辿ってみた続編を書きました。 https://orumin.blogspot.com/2024/06/tar1-tarkey-arguments.html

また、過去ではなく未来方向、つまり Bell Lab で Unix の successor として生まれた Plan 9 や Inferno における tar についてはこちらの lufia 氏のブログでまとめてもらってます! Plan 9とInfernoにおけるtar(1)の変化