h-pod
@h-pod (H. Pod)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

コマンドラインで4096文字を超えるテキストのコピー&ペースト(リダイレクトで保存)をしたい

解決したいこと

コマンドラインで4096文字を超えるテキストのコピー&ペーストをしたいです。

発生している問題・エラー

問題は4096文字を超えた分のテキストがペーストされず、切り捨てられてしまう点です。
特にエラーが発生したりはしません。むしろエラーとして検出できず、データ落ちに気づけないもしくは気づくのが遅れる点が問題といえます。

該当するソースコード

再現手順
(1) Webブラウザで1万文字以上のテキスト(JSONデータ)をコピー (Ctrl+C)
(2) Linux のターミナル(端末アプリ)上で、下記コマンドを実行しておく

$ cat > data.json

(3) 端末上にペースト (Ctrl+Shift+V) し、Ctrl+D (EOF) で終了
(4) 画面上にはペーストしたデータが最後までスクロール表示されます
(5) しかし、data.jsonファイルには4096文字までしか保存されていません

$ wc data.jso
   1    1 4096 data.json

上記(2)から、クリップボード上にはデータはあると判断できます。端末アプリが切り捨てているのかなとも思いましたが、画面上には表示されているので判断できかねています。

なお、(2)のあと echo $? してみると exit code は 0 で正常終了しています。

環境

  • Ubuntu Linux 20.04.5 Desktop LTS 日本語 Remix
  • 同梱の「端末」アプリ
    • GUI上でヘルプ等みても実体がわからないのですが、gnome-terminal コマンドを実行すると起動するので、これだと思います。
$ gnome-terminal --version
# GNOME Terminal 3.36.2 using VTE 0.60.3 +BIDI +GNUTLS +ICU +SYSTEMD
  • cat コマンド
$ cat --version
cat (GNU coreutils) 8.30

自分で試したこと

代替策と質問の動機

代替策として、viエディタを開いて新規ファイルにコピー&ペーストすれば保存することはできましたので、ものすごく困っているわけではないのですが、なぜ4096文字を超えた分が切り捨てられるのか、純粋に疑問が湧いたので質問させていただきました。

問題切り分けのためにやったこと

以下では、この期待通り保存できたファイルを expected.json としています。

cat のファイルI/O, 標準入出力リダイレクトに制限があるかどうか

次のようにファイルのオープンと標準出力へのリダイレクトはできました。

$ cat expected.json > hoge
$ wc hoge
    2     1 80982 hoge

標準入力のバッファーに制限があるのかなと思い、下記のようなパイプ入力も試しましたが、問題が再現しませんでした。

$ cat < expected.json > hoge
$ cat expected.json | cat > fuga
$ wc hoge fuga
     2      1  80982 hoge
     2      1  80982 fuga
     4      2 161964 合計
cat 以外のコマンドの場合

cat 以外にも

$ sed -e 's/,/, /g' >hoge
$ grep '[[:alnum:]]' >fuga

といったコマンドを試しましたが、結果はcatと似たような感じで、入力データが大量に切り捨てられました。

$ wc hoge fuga 
   0  211 4305 hoge
   1    1 4096 fuga
   1  212 8401 合計

ただ、sedの結果からも分かるとおり、出力の文字数は増えていることから、そもそも入力データが来てないような気がしています。

端末アプリ

Desktopの「端末」アプリがバッファーで切り捨てているのかなとも思ったのですが、man gnome-terminal やネット検索してもバッファー関連の情報は見つからず、行き詰まってしまいました。

改行ありの場合

試しにテキスト置換で改行を挿入したデータに加工して同様のコピペを試してみました。

$ cat expected.json |sed -e 's/{/{\n/g' -e 's/}/}\n/g' >foo.json
$ wc foo.json 
 1444  1442 82424 foo.json

このfoo.jsonをWebブラウザで開いて、改めて「端末」へコピー&ペーストすると、期待通りペースト&保存されました。

$ cat > bar.json
$ wc foo.json bar.json 
  1444   1442  82424 foo.json
  1443   1442  82423 bar.json

まとめ

改行がない4096文字超のテキストデータをgnome-terminalへコピー&ペーストできるようにするための方法、もしくはこれが可能な他のターミナルアプリがあれば教えていただきたいです。
また、こういった制限が加えられている一般的な理由などご存知の方がおられましたら教えていただきたいです。

よろしくお願いいたします。

2

6Answer

自分が使用しているのはubuntu22.10ですが、同様の操作で同様に出力が4096バイトになりました。

せっかくなので、色々と試してみました。

xtermをインストールして試してみましたが、やはり4096バイトになりました。

cat > aaa」を実行し「貼り付け」、ではなく、「xclip -o > aaa」すると、16384バイトになりました。

以下のようなPythonスクリプトでテキストファイルを作って、同様に試してみました。

# coding: utf-8

import random

letters = [chr(c) for c in range(ord('a'), ord('z') + 1)]
letters += [chr(c) for c in range(ord('A'), ord('Z') + 1)]
letters += [chr(c) for c in range(ord('0'), ord('9') + 1)]

for i in range(16384 // 4090 + 1):
	print(''.join([random.choice(letters) for j in range(4090)]))

要は、一行4090+(改行)バイトにして16384バイト以上(面倒なのできっちりしないで20455バイト)にして試してみると、20455バイトになりました。
どうやら一ファイルでなく、一行に制限があるっぽい?
(追記: あぁ、改行するとバッファフラッシュするという話もよく聞くので、やはりバッファリングの問題なのかもしれませんね)

ちなみに、この件について調べていると、XWindowSystemには2種類のコピー&ペーストバッファがあるらしいです。
この辺りも関係しているかも?

あと、しれっと書きましたが、xclipというコマンドを使うと便利ですよ。
別途aptでインストールしなければなりませんが。

2Like

Comments

  1. @h-pod

    Questioner

    @katsuko0303 さん、詳細に確認・検証いただき嬉しいです。ありがとうございます!

    xterm/他のターミナルでも起きるということは、X Window Systemの仕様なのかもしれませんね。おかげさまでこういった推測が進展します。助かります。

    `xclip`というのもあるのですね。教えていただきありがとうございます。

    タイミングが前後してしまい私は他の類似ツールに行き着いていたのですが、`xsel` というコマンドもあるようです。インストールして使ってみたところ、やりたいことができました。バッファ/clipboardとのI/Oや標準入出力が1つのコマンドで引数を変えればできるようです。

    https://qiita.com/catatsuy/items/0fd67f706366b2355e8f

    `apt show xsel` と `apt show xclip` を読んでみると、どちらのCLIも `X Selection`という仕組みを使っているようです。

    2種類のバッファがあるとの件、`man xsel` にも同様に PRIMARY と CLIPBOARD の記載があるので同じXの仕組みを使っているのでしょうね。あと、あまり使われることはないらしいのですが、SECONDARY というバッファもあるらしく、実は3つあるようです(豆知識)

    > The X server maintains three selections, called PRIMARY, SECONDARY and CLIPBOARD

    興味深いです。。
    とにかく、助かりました。ありがとうございました!

linuxのパイプのページサイズは4kで、この単位で各コマンドにデータを刻んで渡されます。例えばtail -F で若干表示にタイムラグがある事象です。flushされれば即時渡されます。

cat > data.json
echo ”Hi" | cat - > data.json

そのため、標準入力のバッファーサイズは4kが多いようです。標準出力のバッファーサイズはログを扱う関係で行区切りが多いようです。

stdbuf -iK4 -oK4 -eK4 bash

 だったような?記憶があります。
 -oL 行単位で区切りで、
-iK4 か -i4k かは定かでありません。

1Like

Comments

  1. @h-pod

    Questioner

    @HalHarada さん、コメントありがとうございます。

    `stdbuf` 面白いコマンドですね。`man` で確認して、いくつか試してみました。

    ## バッファサイズを増やしてみる

    8KiBに増やしてみました。しかし結果は変わらず4KiBしかファイルに書き出されませんでした。
    ```
    $ stdbuf -i8K -o8K cat >/tmp/foo
    $ $ wc /tmp/foo
    1 1 4096 /tmp/foo
    ```

    ## 入出力ともにバッファリングをやめてみる

    バッファサイズを0にしてバッファリングをOFFにし、即書き出すようにしてみました。しかしこれも結果は変わらず4KiBしかファイルに書き出されませんでした。
    ```
    $ stdbuf -o0 -i0 cat > /tmp/foo
    $ wc /tmp/foo
    1 1 4096 /tmp/foo
    ```

    ## gnome-terminal 起動時にも stdbuf を適用してみる
    ```
    $ stdbuf -i0 -o0 gnome-terminal
    $ stdbuf -i8K -o8K gnome-terminal
    ```

    上記のいずれも結果は変わらず、4KiBで切られてしまうようです。

    私の理解では、バッファリングは逐次書き出しの処理コストを抑えるためにメモリに一時書き溜めておく仕組みであって、バッファサイズより大きなデータであっても、切り捨てられたりはしない仕組みだと思っています。勘違いでしたらご指摘ください。。

    改行コードを含むデータであれば、改行でバッファがフラッシュされて大量データが切り捨てられずに書き出されているのだと思いますが、なぜ違いが生じているのかがわからないのですよね。

    改行コードを含まない場合に切り捨てられる理由というか、誰が制限をかけているのかがわからないのも困ったところです。

    結果はひとまずさておき、`stdbuf`コマンドのことを教えていただいただけでも嬉しいです。今後問題判別する際にできることが増えたので助かります。ありがとうございます。
  2. @h-pod

    Questioner

    追記:回答への返信でMarkdownが使えないのは知りませんでした。初めて質問したもので、すみません。。
  3. gnome-terminal, catでなく、bashは試しましたか?
  4. @h-pod

    Questioner

    なるほど。すみません、bashを挙げておられた意味をいま理解しました。

    結論から言うと2パターン試したのですが、どちらも状況はかわらず、4Kで切られました。
    * bashとcatの両方のバッファサイズを8Kに増やす
    * bashとcatの両方のバッファサイズを0にしてバッファリングをOFFにする

    以下、詳細です。

    bashとcatの両方を8Kに増やしてみましたが、状況は変わらず、4Kで切られるようです。
    ```
    $ ps aux |grep -E "(stdbuf|bash)"
    somebody 2599 0.0 0.0 9720 5500 pts/0 Ss 07:29 0:00 bash
    somebody 4742 0.0 0.0 7492 2588 pts/0 S+ 07:52 0:00 grep --color=auto -E (stdbuf|bash)

    $ stdbuf -i8K -o8K -e8K bash

    $ ps aux |grep -E "(stdbuf|bash)"
    somebody 2599 0.0 0.0 9720 5500 pts/0 Ss 07:29 0:00 bash
    somebody 4747 0.2 0.0 9664 5504 pts/0 S 07:52 0:00 bash
    somebody 4839 0.0 0.0 7536 736 pts/0 S+ 07:52 0:00 grep --color=auto -E (stdbuf|bash)

    $ stdbuf -i8K -o8K -e8K cat > /tmp/foo

    $ wc /tmp/foo
    1 1 4096 /tmp/foo
    ```

    バッファサイズを0にした場合も試しましたが、結果は同様で、4K切り捨てられました。
    ```
    $ ps aux |grep -E "(stdbuf|bash)"
    somebody 2599 0.0 0.0 9720 5516 pts/0 Ss 07:29 0:00 bash
    somebody 4888 0.0 0.0 7492 2484 pts/0 S+ 07:57 0:00 grep --color=auto -E (stdbuf|bash)

    $ stdbuf -i0 -o0 -e0 bash

    $ ps aux |grep -E "(stdbuf|bash)"
    somebody 2599 0.0 0.0 9720 5516 pts/0 Ss 07:29 0:00 bash
    somebody 4893 0.4 0.0 9640 5332 pts/0 S 07:57 0:00 bash
    somebody 4984 0.0 0.0 7512 2544 pts/0 S+ 07:57 0:00 grep --color=auto -E (stdbuf|bash)

    $ stdbuf -i0 -o0 -e0 cat > /tmp/foo

    $ wc /tmp/foo
    1 1 4096 /tmp/foo
    ```

    謎です^^;

    余談になりますが、子プロセスのbashで親プロセスIDを見てもbashになってるみたいで、stdbuf自体はプロセスとしては存在しないのですね。timeも同様みたいで、このあたりの仕組みも興味深いです
    ```
    $ echo $PPID
    2599

    $ time bash

    $ ps aux |grep -E "(stdbuf|bash|time)"
    systemd+ 710 0.0 0.0 90908 6008 ? Ssl 07:21 0:00 /lib/systemd/systemd-timesyncd
    somebody 1781 0.0 0.2 372496 16840 ? Ssl 07:29 0:00 /usr/libexec/gsd-datetime
    somebody 2599 0.0 0.0 9720 5516 pts/0 Ss 07:29 0:00 bash
    somebody 4893 0.0 0.0 9640 5336 pts/0 S 07:57 0:00 bash
    somebody 5064 0.2 0.0 9640 5448 pts/0 S 08:07 0:00 bash
    somebody 5157 0.0 0.0 7508 2480 pts/0 S+ 08:07 0:00 grep --color=auto -E (stdbuf|bash|time)

    $ exit
    exit

    real 0m45.107s
    user 0m0.288s
    sys 0m0.149s

    ```

    コメントありがとうございます。

もしかして、私の環境固有の問題だったりするのでしょうか?

下記の方法で、4KiBを超える改行なしのテキストデータを作ることができますので、お手数かとは思いますが、興味のある方おられましたら再現するか試してお知らせいただけると助かります。

■改行なしで16Kのテキストデータを作る手順

$ seq -f '%064g' 1 256 |tr -d "\n" >/tmp/expected.txt

$ wc /tmp/expected.txt 
    0     1 16384 /tmp/expected.txt

■(参考)作ったデータの中身の一部を確認する
cut で先頭256バイトのみ切り出してファイル内のデータを確認できます。

$ cut -b 1-256 /tmp/expected.txt 
0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000004

■現象確認手順

# step-1: 端末上で cat を予めフォアグラウンドで実行しておく
$ cat > /tmp/foo

# step-2: 作ったファイル expected.txt をテキストエディタや
#         ブラウザ(URLに file:///tmp/expected.txt を入力)で開いてコピーし、
#         上記端末にペースト

# step-3: Enter, Ctrl+D でファイル入力終了

# step-4: catの出力ファイルのバイト数を表示し、 4Kで切り捨てられるか、
#         あるいは16K出力されるかどうか確認
$ wc -c /tmp/foo

よろしくお願いします。

1Like

Comments

Comments

  1. @h-pod

    Questioner

    @YumaInaura さん、コメントありがとうございます。
    pbpaste/pbcopy は Macで使えるコマンドですね。実は私もMacでは使ってます。

    Ubuntu にはないらしく、調べたら`xsel` が似たようなことができるらしいのでインストールして使ってみました。

    https://qiita.com/catatsuy/items/0fd67f706366b2355e8f

    期待通り切り捨てられずにリダイレクト先へ全バイト書き出せました。テキストエディタを開いてやるよりも直接的でやりやすいですね。ありがとうございます。
    ```
    $ sudo apt install xsel

    $ xsel -o >/tmp/foo

    $ wc /tmp/foo
    0 1 80980 /tmp/foo
    ```

    【余談】
    この質問は、どちらかというと純粋に疑問に思ったからしたのですが、やはり未だに気になっています。

    大量テキストをペーストする場合、
    * 本来その内容はターミナルの画面上に出力する意味はさほどないものだし、
    * 画面描画に時間がかかる、
    といった理由から、
    * 表示上は途中で切り捨てる
    * 一方でクリップボードのデータを黙って全部リダイレクトする
    のであれば自然かなと思うのですが、状況が逆なこともあって気になっている次第です。(そもそも画面描画を消すなんて処理が実装されることはないかとは思いますが・・)

    その後 "gnome-terminal paste 4096" で検索したら、どうやら過去に Postgres や R 言語関連で同様の事象にハマった人がおられたようです。5000文字近い長大なSQL (!?) がコピペできなくてエラーになる、などです。これに対して「console/terminal が途中で切るよ」という旨の回答がされていました。(英語かつ他のQ&Aサイトなのでリンクは割愛します)

    何らかの安全策なのでしょうかね。。この制限やクリップボードのI/Oコマンドを知らない場合はハマりそうです。

    さておき、コメントありがとうございました。
  2. あれ、質問内容ぜんぜんMacじゃなかったですね、失礼しました!
    良いものが見つかったようで良かったです
  3. @h-pod

    Questioner

    いえいえ、お気になさらず。
    pbpasteのことをすっかり忘れていたので、"Linux pbpaste"でググって同様のことを調べた結果、同じ状況になった方の記事を見つけて xsel に行き着けたのですよ。
    助かりました。ありがとうございました!

おそらくX Window Systems の仕様であろうということと、代替策を複数ご提示いただけたので、この質問はクローズさせていただきます。

  1. xclip -o >output
  2. xsel -b -o >output
  3. 改行なしが原因なので、入力データを整形=改行挿入して1行<4096バイトにしてからペーストする
  4. Macの場合、 pbpaste

ご回答いただいた方々、改めてありがとうございました。

1Like

Linuxカーネルにハードコードされている N_TTY_BUF_SIZE (端末のカノニカルモードにおける1行のサイズ)が4096だからのようです。
https://unix.stackexchange.com/questions/131105/how-to-read-over-4k-input-without-new-lines-on-a-terminal/131274#131274

iTerm2→ssh→Linux→catという環境で、コピペせずに4096超の文字を入力してみたところ、4095文字で切り詰められました。なぜ1文字足りないのかが分からないのですが…(改行文字が入っているとかではない)

なお、MacのiTerm2でローカルで同じことをすると、1024バイトまでしか入力できなくなりました(それ以上キーを押しても追加されない)。暗黙に切り詰められるLinuxより問題になりにくいですね。

1Like

Comments

  1. @h-pod

    Questioner

    返答が遅れてすみません。
    カーネルレベルでハードコードされている仕様だったとは…。
    詳しく調査・回答いただきありがとうございます。理解が進み助かります。

    バッファーが固定長配列なのは分かるのですが、それを超えるものを切り捨てるのが謎ですね。バッファにいれて処理したら、バッファをクリアし、入力行のそれ以降のデータをバッファに上書きコピーして処理するのが適切なデータ処理かなと思うのですが、できない事情があるのかもしれませんね。

    確かなのは1行のデータが上限値以下であると仮定して処理されており、上限を超えるものは切り捨てられるということですね。
    カーネルレベルとなると手が出ないので、先述の代替策でしのごうと思います。
    ありがとうございました。

Your answer might help someone💌