ピザって10回表示するbashワンライナーを考える

ふと、ピザって10回表示するのにどういうbashのスクリプトでアプローチ出来るだろうか、と思い立って書いた*しょうもない*記事です。

# for文
35文字。正攻法ですね。

```
# for i in {1..10};do echo pizza;done
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
```

# while文
44文字。seq結果をプロセス置換(Process Substitution)でエセファイル化してます。
キモさは上がったけど文字数が増えた…。

```
# while read ln; do echo pizza;done< <(seq 10)
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
```

# seq置換その1
24文字。seqで表示された数字をpizzaに置換する暴挙。
でも、パイプが気になる…。

```
# seq 10|sed 's/.*/pizza/'
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
```

# seq置換その2
28文字。whileの時のプロセス置換とその1の合わせ技。
文字数は増えたけどパイプやセミコロンがなくなった！！綺麗！！

```
# sed 's/.*/pizza/'< <(seq 10)
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
```

# 番外: Python3の場合
38文字。end=""を入れないと最後が勝手に改行されて空行になってしまう…。
しかし文字列掛け算は強い。

```
# python -c 'print("pizza\n"*10,end="")'
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
```

# 番外: Rubyの場合
27文字。Pythonよりも文字数が少なくなった。
文字列掛け算には勝てない。

```
# ruby -e 'puts "pizza\n"*10'
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
pizza
```

番外の二つと公平を期すならbash -c使えよって感じですが、そこは標準シェルの特権ということで。

# 処理時間どうなの
ということで、いくら書きっぷりが良くても、CPUに優しいスクリプトじゃないとネェ…ということで、
1万回に回数あげて時間も調べました。

```
# time (for i in {1..10000};do echo pizza;done) 1>/dev/null

real    0m0.073s
user    0m0.069s
sys     0m0.004s
```

forはなかなかいいですね。

```
# time (while read ln; do echo pizza; done< <(seq 10000)) 1>/dev/null

real    0m0.150s
user    0m0.113s
sys     0m0.038s
```

while、テメーはだめだ。

```
# time (seq 10000|sed 's/.*/pizza/') 1>/dev/null

real    0m0.009s
user    0m0.009s
sys     0m0.002s
```
**seq置換、はっや…。**

```
# time (sed 's/.*/pizza/'< <(seq 10000)) 1>/dev/null

real    0m0.010s
user    0m0.007s
sys     0m0.002s
```

こちらもはやい。

```
# time (python -c 'print("pizza\n"*10000,end="")') 1>/dev/null

real    0m0.041s
user    0m0.034s
sys     0m0.007s
```

うん…。

```
# time (ruby -e 'puts "pizza\n"*10000') 1>/dev/null

real    0m0.045s
user    0m0.035s
sys     0m0.009s
```

ほぉ…。

# なぜなのか

以下はstrace -cの結果です。

- for

```
# strace -c bash -c "for i in {1..10000};do echo pizza;done 1>/dev/null"
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 99.88    0.019789           2     10000           write
  0.07    0.000013           0        33           brk
  0.03    0.000005           3         2           dup2
  0.01    0.000002           0        10           close
  0.01    0.000002           0         5           fcntl
  0.01    0.000001           0         8           fstat
  0.01    0.000001           0        15           mmap
  0.00    0.000000           0         4           read
  0.00    0.000000           0         9           open
  0.00    0.000000           0        14         5 stat
  0.00    0.000000           0         8           mprotect
  0.00    0.000000           0         2           munmap
  0.00    0.000000           0         8           rt_sigaction
  0.00    0.000000           0         6           rt_sigprocmask
  0.00    0.000000           0         1         1 ioctl
  0.00    0.000000           0         5         1 access
  0.00    0.000000           0         1           getpid
  0.00    0.000000           0         1         1 getpeername
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           uname
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0         5           getuid
  0.00    0.000000           0         5           getgid
  0.00    0.000000           0         5           geteuid
  0.00    0.000000           0         5           getegid
  0.00    0.000000           0         1           getppid
  0.00    0.000000           0         1           getpgrp
  0.00    0.000000           0         1           arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00    0.019813                 10158         8 total
```

個々のwrite処理の時間(usecs/call)は2μ秒という、かなり短い時間での処理ですが、
回数が10000回なので、結果としてかなり時間がかかってしまっています。


- seq sed

```
# strace -c bash -c "sed 's/.*/pizza/'< <(seq 10000) 1>/dev/null"
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 67.13    0.002038        1019         2         1 wait4
  3.95    0.000120           5        24         8 stat
  3.49    0.000106          13         8           open
  3.46    0.000105           8        14           mmap
  2.34    0.000071           9         8           mprotect
  1.91    0.000058           7         8           close
  1.88    0.000057           6        10           rt_sigaction
  1.71    0.000052          13         4           read
  1.71    0.000052           6         9         1 access
  1.48    0.000045          45         1           clone
  1.42    0.000043           4        12           rt_sigprocmask
  1.32    0.000040           4         9           getuid
  1.32    0.000040           4         9           getgid
  1.28    0.000039           6         7           fstat
  1.22    0.000037           4         9           geteuid
  1.19    0.000036           4         9           getegid
  0.96    0.000029          15         2           munmap
  0.82    0.000025           5         5           brk
  0.23    0.000007           7         1           uname
  0.20    0.000006           6         1           rt_sigreturn
  0.20    0.000006           6         1           getpid
  0.20    0.000006           6         1           getrlimit
  0.20    0.000006           6         1           getppid
  0.20    0.000006           6         1           getpgrp
  0.20    0.000006           6         1           arch_prctl
  0.00    0.000000           0         1         1 getpeername
  0.00    0.000000           0         1           execve
------ ----------- ----------- --------- --------- ----------------
100.00    0.003036                   159        11 total
```

こちらは、一番重い処理は1msかかっているものの、2回しか呼ばれていないので、
トータルでは大した時間を消費していません。

当たり前といえば当たり前ですが、forやwhileではド真面目に1万回ループするので、
ループ回数に比例して処理時間が伸びてしまうんですね。おそらく。

従って、ループが少ない場合はむしろforが有利です。

```
# strace -c bash -c "for i in {1..100};do echo pizza;done 1>/dev/null" 2>&1|tail -1
100.00    0.001490                   230         8 total
# strace -c bash -c "sed 's/.*/pizza/'< <(seq 100) 1>/dev/null" 2>&1|tail -1
100.00    0.002294                   159        11 total

```

# 結論
**適 材 適 所**