BATコマンドを読んでてbashのプロセスについて少しだけ詳しくなった
はじめに
遅くなりましたが、ブログを書いていきます。
結論
bash の -c オプションの挙動について気になったので、ソースコードを追いかけました。
シンプルコマンドの場合はforkしないでexecveのみを呼び出していました。
環境
- Proxmox VE
- Ubuntu 24.04
batコマンド
cat コマンドの拡張版でシンタックスハイライトやGitの差分も見れるようになったものです。
───────┬──────────────────────────────────────────────────────────────────────────────────────────────────
│ File: workspace/index.php
───────┼──────────────────────────────────────────────────────────────────────────────────────────────────
1 │ <?php
2 │
3 │ class Hoge {
4 │ function hoge() {}
5 │ };
6 │
7 │ $hoge = new Hoge();
───────┴──────────────────────────────────────────────────────────────────────────────────────────────────
画像も載せておきます。

空白や改行なども全て出力するオプションも用意されています。
変な文字列が紛れていないかを探す時に使えそうです。
───────┬──────────────────────────────────────────────────────────────────────────────────────────────────
│ File: workspace/index.php
───────┼──────────────────────────────────────────────────────────────────────────────────────────────────
1 │ <?php␊
2 │ ␊
3 │ class·Hoge·{␊
4 │ ····function·hoge()·{}␊
5 │ };␊
6 │ ␊
7 │ $hoge·=·new·Hoge();␊
───────┴──────────────────────────────────────────────────────────────────────────────────────────────────
───────┬──────────────────────────────────────────────────────────────────────────────────────────────────
│ File: workspace/index.php
───────┼──────────────────────────────────────────────────────────────────────────────────────────────────
1 │ <?php^J
2 │ ^J
3 │ class·Hoge·{^J
4 │ ····function·hoge()·{}^J
5 │ };^J
6 │ ^J
7 │ $hoge·=·new·Hoge();^J
───────┴──────────────────────────────────────────────────────────────────────────────────────────────────
自動ページング
By default, bat pipes its own output to a pager (e.g. less) if the output is too large for one screen.
If you would rather bat work like cat all the time (never page output), you can set --paging=never as an option, either on the command line or in your configuration file.
If you intend to alias cat to bat in your shell configuration, you can use alias cat='bat --paging=never' to preserve the default behavior.
一画面に収まらない行数の場合は自動でページングで表示してくれます。
表示結果
───────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ File: /etc/skel/.bashrc
───────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ # ~/.bashrc: executed by bash(1) for non-login shells.
2 │ # see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
3 │ # for examples
4 │
5 │ # If not running interactively, don't do anything
6 │ case $- in
7 │ *i*) ;;
8 │ *) return;;
9 │ esac
10 │
11 │ # don't put duplicate lines or lines starting with space in the history.
12 │ # See bash(1) for more options
13 │ HISTCONTROL=ignoreboth
14 │
15 │ # append to the history file, don't overwrite it
16 │ shopt -s histappend
17 │
18 │ # for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
19 │ HISTSIZE=1000
20 │ HISTFILESIZE=2000
21 │
22 │ # check the window size after each command and, if necessary,
23 │ # update the values of LINES and COLUMNS.
:
--paging=neverをつけるとページングしないようにもしてくれます。
表示結果
───────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ File: /etc/skel/.bashrc
───────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ # ~/.bashrc: executed by bash(1) for non-login shells.
2 │ # see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
3 │ # for examples
4 │
5 │ # If not running interactively, don't do anything
6 │ case $- in
7 │ *i*) ;;
8 │ *) return;;
9 │ esac
10 │
11 │ # don't put duplicate lines or lines starting with space in the history.
12 │ # See bash(1) for more options
13 │ HISTCONTROL=ignoreboth
14 │
15 │ # append to the history file, don't overwrite it
16 │ shopt -s histappend
17 │
18 │ # for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
19 │ HISTSIZE=1000
20 │ HISTFILESIZE=2000
21 │
22 │ # check the window size after each command and, if necessary,
23 │ # update the values of LINES and COLUMNS.
24 │ shopt -s checkwinsize
25 │
26 │ # If set, the pattern "**" used in a pathname expansion context will
27 │ # match all files and zero or more directories and subdirectories.
28 │ #shopt -s globstar
29 │
30 │ # make less more friendly for non-text input files, see lesspipe(1)
31 │ [ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"
32 │
33 │ # set variable identifying the chroot you work in (used in the prompt below)
34 │ if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
35 │ debian_chroot=$(cat /etc/debian_chroot)
36 │ fi
37 │
38 │ # set a fancy prompt (non-color, unless we know we "want" color)
39 │ case "$TERM" in
40 │ xterm-color|*-256color) color_prompt=yes;;
41 │ esac
42 │
43 │ # uncomment for a colored prompt, if the terminal has the capability; turned
44 │ # off by default to not distract the user: the focus in a terminal window
45 │ # should be on the output of commands, not on the prompt
46 │ #force_color_prompt=yes
47 │
48 │ if [ -n "$force_color_prompt" ]; then
49 │ if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
50 │ # We have color support; assume it's compliant with Ecma-48
51 │ # (ISO/IEC-6429). (Lack of such support is extremely rare, and such
52 │ # a case would tend to support setf rather than setaf.)
53 │ color_prompt=yes
54 │ else
55 │ color_prompt=
56 │ fi
57 │ fi
58 │
59 │ if [ "$color_prompt" = yes ]; then
60 │ PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
61 │ else
62 │ PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
63 │ fi
64 │ unset color_prompt force_color_prompt
65 │
66 │ # If this is an xterm set the title to user@host:dir
67 │ case "$TERM" in
68 │ xterm*|rxvt*)
69 │ PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
70 │ ;;
71 │ *)
72 │ ;;
73 │ esac
74 │
75 │ # enable color support of ls and also add handy aliases
76 │ if [ -x /usr/bin/dircolors ]; then
77 │ test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
78 │ alias ls='ls --color=auto'
79 │ #alias dir='dir --color=auto'
80 │ #alias vdir='vdir --color=auto'
81 │
82 │ alias grep='grep --color=auto'
83 │ alias fgrep='fgrep --color=auto'
84 │ alias egrep='egrep --color=auto'
85 │ fi
86 │
87 │ # colored GCC warnings and errors
88 │ #export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'
89 │
90 │ # some more ls aliases
91 │ alias ll='ls -alF'
92 │ alias la='ls -A'
93 │ alias l='ls -CF'
94 │
95 │ # Add an "alert" alias for long running commands. Use like so:
96 │ # sleep 10; alert
97 │ alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+
│ \s*//;s/[;&|]\s*alert$//'\'')"'
98 │
99 │ # Alias definitions.
100 │ # You may want to put all your additions into a separate file like
101 │ # ~/.bash_aliases, instead of adding them here directly.
102 │ # See /usr/share/doc/bash-doc/examples in the bash-doc package.
103 │
104 │ if [ -f ~/.bash_aliases ]; then
105 │ . ~/.bash_aliases
106 │ fi
107 │
108 │ # enable programmable completion features (you don't need to enable
109 │ # this, if it's already enabled in /etc/bash.bashrc and /etc/profile
110 │ # sources /etc/bash.bashrc).
111 │ if ! shopt -oq posix; then
112 │ if [ -f /usr/share/bash-completion/bash_completion ]; then
113 │ . /usr/share/bash-completion/bash_completion
114 │ elif [ -f /etc/bash_completion ]; then
115 │ . /etc/bash_completion
116 │ fi
117 │ fi
───────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
自分環境でのページングの実行結果
$ pstree
...
systemd─┬─2*[agetty]
...
├─sshd─┬─sshd───sshd───bash───bat───less
│ └─sshd───sshd───bash───pstree
...
裏側で less が呼ばれていました。
$ strace -f -o bat_trace2 bat /etc/skel/.profile でシステムコール単位でbatを追いかけてみました。
186063 execve("/home/dev/.local/bin/bat", ["bat", "/etc/skel/.profile"], 0x7ffd07d12d20 /* 27 vars */) = 0
186064 execve("/home/dev/.local/bin/less", ["less", "--version"], 0x7ffc755e4190 /* 27 vars */) = -1 ENOENT (No such file or directory)
186064 execve("/home/dev/.local/share/mise/installs/node/24.9.0/bin/less", ["less", "--version"], 0x7ffc755e4190 /* 27 vars */) = -1 ENOENT (No such file or directory)
186064 execve("/home/dev/.cargo/bin/less", ["less", "--version"], 0x7ffc755e4190 /* 27 vars */) = -1 ENOENT (No such file or directory)
186064 execve("/usr/local/sbin/less", ["less", "--version"], 0x7ffc755e4190 /* 27 vars */) = -1 ENOENT (No such file or directory)
186064 execve("/usr/local/bin/less", ["less", "--version"], 0x7ffc755e4190 /* 27 vars */) = -1 ENOENT (No such file or directory)
186064 execve("/usr/sbin/less", ["less", "--version"], 0x7ffc755e4190 /* 27 vars */) = -1 ENOENT (No such file or directory)
186064 execve("/usr/bin/less", ["less", "--version"], 0x7ffc755e4190 /* 27 vars */) = 0
186064 +++ exited with 0 +++
186063 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=186064, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
186065 execve("/home/dev/.local/bin/less", ["less", "-R", "-F"], 0x5634e9e79a80 /* 28 vars */) = -1 ENOENT (No such file or directory)
186065 execve("/home/dev/.local/share/mise/installs/node/24.9.0/bin/less", ["less", "-R", "-F"], 0x5634e9e79a80 /* 28 vars */) = -1 ENOENT (No such file or directory)
186065 execve("/home/dev/.cargo/bin/less", ["less", "-R", "-F"], 0x5634e9e79a80 /* 28 vars */) = -1 ENOENT (No such file or directory)
186065 execve("/usr/local/sbin/less", ["less", "-R", "-F"], 0x5634e9e79a80 /* 28 vars */) = -1 ENOENT (No such file or directory)
186065 execve("/usr/local/bin/less", ["less", "-R", "-F"], 0x5634e9e79a80 /* 28 vars */) = -1 ENOENT (No such file or directory)
186065 execve("/usr/sbin/less", ["less", "-R", "-F"], 0x5634e9e79a80 /* 28 vars */) = -1 ENOENT (No such file or directory)
186065 execve("/usr/bin/less", ["less", "-R", "-F"], 0x5634e9e79a80 /* 28 vars */) = 0
186063 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} ---
186065 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} ---
186065 +++ exited with 0 +++
186063 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=186065, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
186063 +++ exited with 0 +++
様々なlessが呼び出されていました。
batは/usr/bin/lessがないような様々な環境で実行できることを想定して手当たり次第にlessの実行を試みているのかなと思いを馳せました。
less コマンドについても$ strace -f -o less_trace -e execve less /etc/skel/.profileでシステムコール単位で調べました。
172035 execve("/usr/bin/less", ["less", "/etc/skel/.profile"], 0x7ffe70a446c0 /* 27 vars */) = 0
172036 execve("/bin/sh", ["sh", "-c", "--", "/bin/bash -c \\ /usr/bin/lesspipe"...], 0x7ffc12071f00 /* 27 vars */) = 0
172037 execve("/bin/bash", ["/bin/bash", "-c", " /usr/bin/lesspipe /etc/skel/.pr"...], 0x64e59318b958 /* 27 vars */) = 0
172037 execve("/usr/bin/lesspipe", ["/usr/bin/lesspipe", "/etc/skel/.profile"], 0x56af5e244620 /* 27 vars */) = 0
172038 execve("/usr/bin/basename", ["basename", "/usr/bin/lesspipe"], 0x60843bb20a38 /* 27 vars */) = 0
172038 +++ exited with 0 +++
172037 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=172038, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
172041 +++ exited with 0 +++
172040 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=172041, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
172042 execve("/usr/bin/tr", ["tr", "[:upper:]", "[:lower:]"], 0x60843bb2c9d0 /* 27 vars */) = 0
172042 +++ exited with 0 +++
172040 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=172042, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
172040 +++ exited with 0 +++
172039 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=172040, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
172039 +++ exited with 0 +++
172037 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=172039, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
172037 +++ exited with 0 +++
172036 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=172037, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
172036 +++ exited with 0 +++
172035 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=172036, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
172035 --- SIGWINCH {si_signo=SIGWINCH, si_code=SI_KERNEL} ---
172035 +++ exited with 0 +++
ここで、bashからlessを呼び出しているのに、同じPID172037になっているのが気になりました。
172037 execve("/bin/bash", ["/bin/bash", "-c", " /usr/bin/lesspipe /etc/skel/.pr"...], 0x64e59318b958 /* 27 vars */) = 0
172037 execve("/usr/bin/lesspipe", ["/usr/bin/lesspipe", "/etc/skel/.profile"], 0x56af5e244620 /* 27 vars */) = 0
自分の知識では、基本的にshellはcdなどのbuiltinのコマンド以外は別プロセスを生み出すと思っていたので、すごく気になりました。
builtinかどうかチェックするコマンド
$ type cd
cd is a shell builtin
$ type ls
ls is /bin/ls
なぜ同じプロセスになっている
この部分だけに注目して流れを追っていきます。
172035 execve("/usr/bin/less", ["less", "/etc/skel/.profile"], 0x7ffe70a446c0 /* 27 vars */) = 0
172036 execve("/bin/sh", ["sh", "-c", "--", "/bin/bash -c \\ /usr/bin/lesspipe"...], 0x7ffc12071f00 /* 27 vars */) = 0
172037 execve("/bin/bash", ["/bin/bash", "-c", " /usr/bin/lesspipe /etc/skel/.pr"...], 0x64e59318b958 /* 27 vars */) = 0
172037 execve("/usr/bin/lesspipe", ["/usr/bin/lesspipe", "/etc/skel/.profile"], 0x56af5e244620 /* 27 vars */) = 0
まずは、lessプロセスの中でshプロセスが生成されています。
172035 execve("/usr/bin/less", ["less", "/etc/skel/.profile"], 0x7ffe70a446c0 /* 27 vars */) = 0
172036 execve("/bin/sh", ["sh", "-c", "--", "/bin/bash -c \\ /usr/bin/lesspipe"...], 0x7ffc12071f00 /* 27 vars */) = 0
続いて、shプロセスからbashプロセスが生成されています。
172036 execve("/bin/sh", ["sh", "-c", "--", "/bin/bash -c \\ /usr/bin/lesspipe"...], 0x7ffc12071f00 /* 27 vars */) = 0
172037 execve("/bin/bash", ["/bin/bash", "-c", " /usr/bin/lesspipe /etc/skel/.pr"...], 0x64e59318b958 /* 27 vars */) = 0
そして、bashプロセスがlesspipieプロセスに生まれ変わっています。
172037 execve("/bin/bash", ["/bin/bash", "-c", " /usr/bin/lesspipe /etc/skel/.pr"...], 0x64e59318b958 /* 27 vars */) = 0
172037 execve("/usr/bin/lesspipe", ["/usr/bin/lesspipe", "/etc/skel/.profile"], 0x56af5e244620 /* 27 vars */) = 0
bash
-cが怪しいです。
まずは、man コマンドでbashについて見てみます。
-c If the -c option is present, then commands are read from the first non-option argument command_string. If there are ar‐
guments after the command_string, the first argument is assigned to $0 and any remaining arguments are assigned to the
positional parameters. The assignment to $0 sets the name of the shell, which is used in warning and error messages.
シェルに渡すコマンドを文字列から受け取れるオプションのようです。
$ bash -c 'sleep 100'
しかし、どこにもプロセスIDについての記載がないので、裏側でどのような処理になっているかソースコードから追いかけてみました。
ソースコードは
を参照しました。
ソースコードを追いかける
最初にざっくり概要をまとめておくと、
のようになっています。
それでは実際に追いかけていきます。
まず、config-top.hに
/* Define ONESHOT if you want sh -c 'command' to avoid forking to execute
`command' whenever possible. This is a big efficiency improvement. */
#define ONESHOT
と記載がされているので-cオプションを指定した際は最適化のために可能な限りforkなしで実行する旨が書かれています。
さらに、読み進めていきます。
shell.cにmain関数があり、その中でONESHOTが定義されているときはrun_one_command (command_execution_string);を実行する処理になっています。
int
main (int argc, char **argv, char **env)
...
#if defined (ONESHOT)
executing = shell_initialized = 1;
run_one_command (command_execution_string);
exit_shell (last_command_exit_value);
#else /* ONESHOT */
...
#endif /* !ONESHOT */
run_one_commandではparse_and_executeを実行しています。
#if defined (ONESHOT)
/* Run one command, given as the argument to the -c option. Tell
parse_and_execute not to fork for a simple command. */
static int
run_one_command (char *command)
{
int code;
...
return (parse_and_execute (savestring (command), "-c", SEVAL_NOHIST|SEVAL_RESETLINE));
}
#endif /* ONESHOT */
parse_and_executeの中では、
command->value.Simple->flags |= CMD_NO_FORK;
でcommandが設定されて、
int pipe_in = NO_PIPE
int pipe_out = NO_PIPE
でexecute_command_internalを実行しています。
int
parse_and_execute (char *string, const char *from_file, int flags)
{
int code, lreset, ignore_return;
volatile int should_jump_to_top_level, last_result;
COMMAND *volatile command;
volatile sigset_t pe_sigmask;
...
#if defined (ONESHOT)
/*
* IF
* we were invoked as `bash -c' (startup_state == 2) AND
* parse_and_execute has not been called recursively AND
* we're not running a trap AND
* we have parsed the full command (string == '\0') AND
* we're not going to run the exit trap AND
* we have a simple command without redirections AND
* the command is not being timed AND
* the command's return status is not being inverted AND
* there aren't any traps in effect
* THEN
* tell the execution code that we don't need to fork
*/
if (should_suppress_fork (command))
{
command->flags |= CMD_NO_FORK;
command->value.Simple->flags |= CMD_NO_FORK;
}
...
#endif /* ONESHOT */
...
last_result = execute_command_internal
(command, 0, NO_PIPE, NO_PIPE, bitmap);
...
execute_command_internalでは、execute_simple_commandを実行しています。
/* Execute the command passed in COMMAND, perhaps doing it asynchronously.
COMMAND is exactly what read_command () places into GLOBAL_COMMAND.
ASYNCHRONOUS, if non-zero, says to do this command in the background.
PIPE_IN and PIPE_OUT are file descriptors saying where input comes
from and where it goes. They can have the value of NO_PIPE, which means
I/O is stdin/stdout.
FDS_TO_CLOSE is a list of file descriptors to close once the child has
been forked. This list often contains the unusable sides of pipes, etc.
EXECUTION_SUCCESS or EXECUTION_FAILURE are the only possible
return values. Executing a command with nothing in it returns
EXECUTION_SUCCESS. */
int
execute_command_internal (COMMAND *command, int asynchronous, int pipe_in, int pipe_out, struct fd_bitmap *fds_to_close)
{
...
switch (command->type)
{
case cm_simple:
...
exec_result =
execute_simple_command (command->value.Simple, pipe_in, pipe_out,
asynchronous, fds_to_close);
...
break;
case cm_for:
...
execute_simple_commandではexecute_disk_commandを実行しています。
/* The meaty part of all the executions. We have to start hacking the
real execution of commands here. Fork a process, set things up,
execute the command. */
static int
execute_simple_command (SIMPLE_COM *simple_command, int pipe_in, int pipe_out, int async, struct fd_bitmap *fds_to_close)
{
WORD_LIST *words, *lastword;
char *command_line, *lastarg, *temp;
int first_word_quoted, result, builtin_is_special, already_forked, dofork;
int fork_flags, cmdflags;
...
cmdflags = simple_command->flags;
...
result = execute_disk_command (words, simple_command->redirects, command_line,
pipe_in, pipe_out, async, fds_to_close,
cmdflags);
execute_disk_commandで、パイプの設定からforkするかしないかを決定し、shell_execveを実行します。
ちなみに、forkはmake_childの中で行われています。
static int
execute_disk_command (WORD_LIST *words, REDIRECT *redirects, char *command_line,
int pipe_in, int pipe_out, int async,
struct fd_bitmap *fds_to_close, int cmdflags)
{
char *pathname, *command, **args, *p;
int nofork, stdpath, result, fork_flags;
pid_t pid;
...
nofork = (cmdflags & CMD_NO_FORK); /* Don't fork, just exec, if no pipes */
...
/* We have to make the child before we check for the non-existence
of COMMAND, since we want the error messages to be redirected. */
/* If we can get away without forking and there are no pipes to deal with,
don't bother to fork, just directly exec the command. */
if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE)
pid = 0;
else
{
fork_flags = async ? FORK_ASYNC : 0;
pid = make_child (p = savestring (command_line), fork_flags);
}
if (pid == 0)
{
int old_interactive;
...
/* Execve expects the command name to be in args[0]. So we
leave it there, in the same format that the user used to
type it in. */
args = strvec_from_word_list (words, 0, 0, (int *)NULL);
exit (shell_execve (command, args, export_env));
}
shell_execveでexecveが呼ばれています。
/* Call execve (), handling interpreting shell scripts, and handling
exec failures. */
int
shell_execve (char *command, char **args, char **env)
{
int i, fd, sample_len;
char sample[HASH_BANG_BUFSIZ];
size_t larray;
SETOSTYPE (0); /* Some systems use for USG/POSIX semantics */
execve (command, args, env);
i = errno; /* error from execve() */
CHECK_TERMSIG;
SETOSTYPE (1);
...
forkなしでexecveを呼び出しているのでPIDが変わらなさそうです。
まとめ
今回は、batコマンドの気になる部分を追いかけていくとbashの-cオプションについて少し詳しく慣れました。
bashが最適化のためにforkなしでコマンドを実行していることが知れて良い機会でした。
dash -cは内部挙動が変わってそうなので、それもいつか追いかけてみたいです。
また、lesspipeについて気になったので、lesspipeについても調べてみます。
Discussion