Expand
- readline
- rl_clear_history
- rl_on_new_line
- rl_replace_line
- rl_redisplay
- add_history
- printf
- malloc
- free
- write
- access
- open
- read
- close
- fork
- wait
- waitpid
- wait3
- wait4
- signal
- sigaction
- kill
- exit
- getcwd
- chdir
- stat
- lstat
- fstat
- unlink
- execve
- dup
- dup2
- pipe
- opendir
- readdir
- closedir
- strerror
- perror
- isatty
- ttyname
- ttyslot
- ioctl
- getenv
- tcsetattr
- tcgetattr
- tgetent
- tgetflag
- tgetnum
- tgetstr
- tgoto
- tputs
First things first, read the Bash manual.
When creating forks and pipes, if the first executable is a builtin, bash executes it in its main process. If a builtin is not the first executable, bash executes it in a child process.
To avoid memory leak of readline being reported by valgrind
valgrind --suppressions=readline.supp --leak-check=full --show-leak-kinds=all ./minishell
Thanks to JonathanDUFOUR
- SHLVL environment variable that is incremented for each shell child process (bash/zsh/maybe others)
- Unique pipe – pipeline is implemented one pipe at a time (like in microshell)
Everything you will see down there is either written in the Bash manual, the readline manual or the libc manual.
cat | cat | ls
Good
<enter> <enter>
Bad
<enter> <enter> <enter> ... <CTRL-D>
How to fix
It is likely that you are not be closing enough file descriptors (especially pipes) in your sub processes.
Make sure you close
every file descriptor as soon as you don't need them anymore.
Please keep in mind that child process created with fork
copies its parent's memory, including signal handlers and file descriptors.
<< $sep
hello
world
heredoc
$sep
Your heredocs must break when reading the literal separator (no expansion is performed on here-document separators)
<< 'SEP'
'PATH: $PATH'
SEP
# output: 'PATH: $PATH'
<< SEP
'PATH: $PATH'
SEP
# output: 'PATH: /bin:/usr/bin:...'
These two here documents do not produce the same result. When the heredoc separator contains quoted words (being empty, simple or composite), your heredoc must NOT expand variables.
export FILE='this is a file'
echo Hello World > $FILE
ls -l
#...
#-rw-r--r-- 1 user42 2021_paris 0 Apr 3 08:37 'this is a file'
#...
# Same goes for '<' and '>>'
> test.out
>> test.out
< Makefile | cat
pwd | > test.out
pwd | < Makefile
pwd | >> test.out < Makefile | cat | >> test2.out | rev
# all these inputs are valid
The message you print does not really matter as long as it is relevant. What is more important is the status you will return if something goes wrong.
./NotFound # 'Command not found' => 127
./NotExecutable # 'Permission denied' => 126
/home/ # 'Is a directory' or 'Permission denied' => 126
| cat # 'Syntax error' => 2
If your command gets terminated or stopped by a signal, you must exit with 128
+ signal
(for example, SIGINT
on Linux is 2
so it must exit with 128 + 2 = 130
).
You should use WIF*
macros (defined in <wait.h>
) to get the real exit status of a process when using wait
or waitpid
. See man 2 waitpid.
Fork in loop, then wait in another. That way, commands may run in parallel.
int pid;
int status;
while (condition)
{
// pipe here
pid = fork();
if (pid == -1)
handle_error();
if (pid == 0)
execute_child();
}
while (condition)
{
pid = wait(&status); // wait for any child
if (pid == -1)
handle_error();
if (check_last_pid(pid))
{
if (WIFEXITED(status))
g_exit_status = WEXITSTATUS(status);
else if (WIFSIGNALED(status))
g_exit_status = 128 + WTERMSIG(status);
else if (WIFSTOPPED(status))
g_exit_status = 128 + WSTOPSIG(status);
else
g_exit_status = status;
}
}