lsコマンドはlsだけだと改行がないが、リダイレクトやパイプをすると改行がつくのはなぜだろうか。その謎に迫る。

Linux使いならlsを使わない日はないだろう。
lsのオプションで大体-lをつける人が多いかもしれないが、
たまにはlsでオプションをつけない奴も実行してみてほしい。
ちょっと不思議な動作をしている。

$ls
 foobar1   foobar2  foobar4  foobar6  foobar8  foobardasdadada
 foobar10  foobar3  foobar5  foobar7  foobar9

上記のようにlsだけを打てば横にずらずらと書かれる

一方で、おなじくオプションをつけないlsの結果をリダイレクトをすると、ファイルが一つずつ改行されて表示される。

$ls > foobar.txt
$cat foobar.txt 
foobar.txt
foobar1
foobar10
foobar2
foobar3
foobar4
foobar5
foobar6
foobar7
foobar8
foobar9
foobardasdadada
$

コンソールに出力されているものと、リダイレクトされるもので内容が変わっている

リダイレクトとパイプが改行で区切られるようになっている

ファイルが一つずつ改行されるモードはオプションでは-1というものがある。
数字の1だ。
ls -1 で実行すると一つずつ改行される。
リダイレクトされた内容はまさにそれと同じものだった。

$ls | hexdump -C
00000000  66 6f 6f 62 61 72 2e 74  78 74 0a 66 6f 6f 62 61  |foobar.txt.fooba|
00000010  72 31 0a 66 6f 6f 62 61  72 31 30 0a 66 6f 6f 62  |r1.foobar10.foob|
00000020  61 72 32 0a 66 6f 6f 62  61 72 33 0a 66 6f 6f 62  |ar2.foobar3.foob|
00000030  61 72 34 0a 66 6f 6f 62  61 72 35 0a 66 6f 6f 62  |ar4.foobar5.foob|
00000040  61 72 36 0a 66 6f 6f 62  61 72 37 0a 66 6f 6f 62  |ar6.foobar7.foob|
00000050  61 72 38 0a 66 6f 6f 62  61 72 39 0a 66 6f 6f 62  |ar8.foobar9.foob|
00000060  61 72 64 61 73 64 61 64  61 64 61 0a              |ardasdadada.|

パイプで値を渡してみる。
上記の場合はパイプでhexdumpコマンドに渡して、内容を表示するようにした。
すると、ところどころに0aという値が入っている。
これは改行コードのlfを表している。

つまり、コンソールと、リダイレクト・パイプがそれぞれ内容がちがう出力をしている

lsのソースに答えを求めた

ああだこうだ色々考えても仕方がない。
我らLinuxとそのGnuツール群はオープンソースなのだから、
動作に気になるところがあれば、ソースを見ればいい

githubが正ではないみたいだが、ミラーみたいな扱いとしてgithubでも公開されていた。
https://github.com/coreutils/coreutils

src/ls.c にlsのソースがある。

出力先の判定をしているところを見つけた

僕はC言語を使える人間ではないので、それっぽいところから探していく。
decode_switches関数付近にそれらしい処理を見つけた。

    case LS_LS:
      /* This is for the 'ls' program.  */
      if (isatty (STDOUT_FILENO))
        {
          format = many_per_line;
          set_quoting_style (NULL, shell_escape_quoting_style);
          /* See description of qmark_funny_chars, above.  */
          qmark_funny_chars = true;
        }
      else
        {
          format = one_per_line;
          qmark_funny_chars = false;
        }
      break;

one_per_lineとは、一行につき一つという意味で、もう一つのmany_per_lineとは一行につき沢山って意味だろう。

one_per_lineは-1オプションをつけたときに有効になるようなので、
まさにファイル一つずつに改行モードを示している

        case '1':
          /* -1 has no effect after -l.  */
          if (format != long_format)
            format = one_per_line;
          break;

つまり以下の部分で、改行を入れるモードの制御を行っているということだ。

     if (isatty (STDOUT_FILENO))
        {
          format = many_per_line;
          set_quoting_style (NULL, shell_escape_quoting_style);
          /* See description of qmark_funny_chars, above.  */
          qmark_funny_chars = true;
        }
      else

isatty (STDOUT_FILENO) という関数で何が行われるのか。
https://linuxjm.osdn.jp/html/LDP_man-pages/man3/isatty.3.html
ここのページでみてみると、
ファイルディスクリプターが端末を参照しているかどうかを調べてくれるらしい。
なんのこっちゃわからない説明だ。

http://x68000.q-e-d.net/~68user/unix/pickup?isatty
こちらのページをみてみると、出力がコンソールかパイプかがわかるという例でほぼ同じコードが載っている。

つまり、isattyでコンソールかパイプかを振り分けて、出力結果を変えているのだ。

スポンサードリンク

関連コンテンツ