0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ENAMETOOLONG / Argument list too long は「コマンドの長さ」が原因だった

0
Posted at

この記事の対象

  • ENAMETOOLONG: name too long, uv_spawn が出て、原因の見当がつかない方
  • bash: Argument list too long に遭遇した方
  • ローカルでは動くのに、CI やサーバーでだけ同じスクリプトが落ちる方

私は本業がエンジニアではなく、Claude Code を相棒にスクリプトを書いたりサーバーを触ったりしている立場です。先日この2つのエラーで丸ごと足止めを食らい、原因を追ったので、同じ場所で止まる人のために整理しておきます。

結論だけ先に書くと、この2つは同じ原因の別表現で、悪いのはコマンドの中身ではなく コマンド全体の長さ です。

原因: exec に渡せるサイズの上限を超えている

プロセスを起動するとき、OS は次の3つをまとめて受け取ります。

渡すもの 例
実行ファイル名 /usr/bin/python3
引数の配列 script.py, --input, (長大なデータ)
環境変数の配列 PATH=..., LANG=..., ...

この3つの合計にサイズ上限があり、超えると execve(2) の時点で E2BIG として弾かれます。プログラムは1行も実行されません。つまり構文が完璧でも、長いというだけで落ちます。

Node.js から子プロセスを起動した場合、この失敗が libuv の uv_spawn 経由で表に出てくるため、ENAMETOOLONG: name too long, uv_spawn という**「名前が長い」と読めてしまう**メッセージになります。ファイルパスを疑ってフォルダを掘り返しても何も見つからないのは、そもそも見る場所が違うからです。

上限値の確認方法

# Linux / macOS: 引数+環境変数の合計上限
getconf ARG_MAX

# 今の環境変数が何バイト占めているか
env | wc -c
環境 目安
Windows コマンドライン全体で約 32,767 文字(OS 仕様として固定)
Linux / macOS 数十万バイト〜2MB 程度(getconf ARG_MAX)

Linux 系は数字だけ見ると余裕がありますが、環境変数が同じ枠を食うことに注意してください。「ローカルでは通るのに CI で落ちる」の大半はこれです。CI コンテナは環境変数が多く、実際に使える引数長がローカルより短くなっています。

再現したケース

私が踏んだのは、300行程度のデータをヒアドキュメントでファイルに落とそうとした場面でした。

cat > data.tsv <<'EOF'
(300行ぶんのデータ)
EOF

これ単体なら何の問題もありません。問題は、このスクリプト全体を1つのコマンド文字列としてプロセス起動の引数に渡していたことです。OS 側から見れば、300行のデータがそのまま引数として積まれた状態になります。

ENAMETOOLONG: name too long, uv_spawn

厄介なのは、これがシェルからのエラーではない点です。シェルの構文エラーなら行番号が出ますが、ここではシェル自体が起動していないので行番号も文脈も出ません。「どの行が悪いのか」を探し始めると、いくら読んでも答えが出ません。

対処法

1. データをファイルに書き、パスだけ渡す

もっとも確実です。渡す文字列が数十バイトに縮むので、上限には事実上当たりません。

# NG: データそのものを引数に載せる
python script.py "$(cat huge.tsv)"

# OK: パスだけ渡す
python script.py huge.tsv

私のケースもこれで解決しました。ヒアドキュメントで完結させるのを諦め、ファイル書き込みに切り替えただけです。

2. 標準入力で流す

引数ではなく stdin であれば ARG_MAX は無関係です。データ量が増えても壊れないので、長期的にはこれが一番安全です。

generate_data | python script.py
import sys
for line in sys.stdin:
    ...
// Node.js
let buf = '';
process.stdin.on('data', c => buf += c);
process.stdin.on('end', () => { /* ... */ });

3. xargs で分割する

「対象ファイルが数万件あって、全部引数に並べたら落ちた」パターン向けです。xargs は ARG_MAX に収まるサイズへ自動的に小分けして、コマンドを繰り返し実行します。

find . -name '*.log' -print0 | xargs -0 grep -l ERROR

ファイル名に空白が含まれうる場合は -print0 と -0 をセットで指定します。片方だけだと、空白入りのファイル名が2つに割れて別ファイル扱いになります。

xargs はコマンドを複数回実行します。wc -l のように結果を合算したい処理や、冪等でない処理には向きません。「合計行数を数えたつもりが、分割ぶんの小計が複数出てきた」というのは典型的な事故です。

4. 環境変数を削る(サーバーでだけ落ちる場合の応急処置)

# 両環境で比較する
env | wc -c
getconf ARG_MAX

環境変数が肥大している環境では、不要なものを外すだけで通ることがあります。ただしこれは枠を少し取り戻すだけの応急処置です。データ量が増えれば再発するので、最終的には 1 か 2 に寄せるべきです。

遠回りしやすいポイント

やりがちな行動 なぜ無駄か
変数名を短くする・コマンドを圧縮する 上限に対して数十文字の節約は誤差
パスが長いと思ってディレクトリを移動する ENAMETOOLONG という名前に引きずられた誤診
怪しい行をコメントアウトして二分探索する プロセスが起動していないので、行単位の切り分けが原理的に成立しない

私は3番目をやりかけました。エラーメッセージが指していたのは中身ではなく「器」の方だった、というだけの話でした。

まとめ

症状 原因 対処
ENAMETOOLONG: name too long, uv_spawn 子プロセス起動時の引数が長すぎる データをファイルに書き、パスだけ渡す
Argument list too long 引数+環境変数が ARG_MAX 超過 標準入力に切り替える/xargs で分割
ローカルで通り CI・サーバーで落ちる 環境変数が枠を圧迫している 両環境で env | wc -c を比較。根本対処は上の2つ
行番号が出ず切り分けできない シェルが起動していない 中身ではなく全体の長さを見る

原則としては1行で済みます。

長いデータは引数ではなく、ファイルか標準入力で渡す。

最初からこうしておけば、ARG_MAX の値を調べる必要すらありません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?