The Shell Program
Sources:
- Machtelt Garrels. Chapter 1. Chapter 1. Bash and Bash scripts.
- 蔡德明. 第十一认识与学习Bash. 鸟哥的Linux私房菜.
The Shell Program
The concept of shell
At a high level, a "shell" program refers to any program that acts as an intermediary between the kernel and user applications. It operates at the lowest level of user-mode software, providing the primary interface for system interaction.
A shell is specifically a command interpreter - a program that takes user input (text commands or graphical actions), interprets and transfers them to the operating system to perform.
All shells provide a user interface, but the interface type varies:
- Command line interfaces (CLI) accept text commands
- Graphical interface (GUI) accept graphical actions (clicks, gestures)
1 | Operating System Kernel |
Both interface types perform the same core function - command interpretation:
1 | Text-based Shell (bash/zsh): |
Popular shell categories
Command-Line Shells:
- bash, zsh, fish (Linux/macOS)
- PowerShell, cmd (Windows)
Graphical Shells:
- GNOME Shell, Plasma Shell (Linux desktop environments)
- Windows Explorer (Windows desktop)
- Finder (macOS desktop)
This chapter focuses on Unix Command-Line shells (bash, zsh, etc.), though similar concepts apply to other operating systems like Microsoft Windows with PowerShell.
Different shell implementations create different command dialects and feature sets, but the fundamental concepts remain consistent across platforms. Once you understand one shell well, transitioning to others becomes much easier.
sh, bash and zsh
sh(Bourne Shell): The original UNIX shell, still available on every system for compatibility. It's a basic shell with minimal features but forms the foundation for other shells.bash(Bourne Again Shell): The standard shell on most Linux distributions and the default for most users. It's a superset of the Bourne shell, meaning allshcommands work inbash, but with many additional features.zsh(Z Shell): A modern shell with advanced features like better autocompletion, themes, and extensive customization options.
The sh command has been largely replaced by different implementations across modern systems:
- Ubuntu/Debian: From Debian Squeeze onward,
/bin/shpoints todash(Debian Almquist Shell) for better performance and POSIX compliance. Previously it wasbash. - macOS: On macOS,
/bin/shis still interpreted bybash(version 3.2), though Apple has addeddashand may eventually switch to it. - Other Linux Distributions: On many Linux systems,
/bin/shis still a symbolic link tobash, though this varies by distribution.
The /etc/shells file lists all available shells on the system (Linux and macOS):
1 | ❯ cat /etc/shells |
All examples in this post use bash since it's the most widely available and commonly used shell.
Shell session types
Shells operate in different modes based on how they're invoked:
Login vs Non-Login
- Login Shell: Started when logging into the system from scratch
- Examples: SSH sessions (
ssh user@server), console login (Ctrl+Alt+F1),bash --login - Sets up complete user environment, reads login-specific config files
- First character of
$0, i.e., the name of the shell, is-(shows as-bash)
- Examples: SSH sessions (
- Non-Login Shell: Started when already logged into the system
- Examples: New terminal windows in desktop, running scripts, VS Code terminal,
bashfrom bash - Inherits environment from parent shell, reads session-specific files
- Examples: New terminal windows in desktop, running scripts, VS Code terminal,
Interactive vs Non-Interactive
- Interactive Shell: Accepts user commands with prompt, provides editing/history/completion
- Non-Interactive Shell: Executes scripts without user interaction, optimized for automation
Text-based vs Graphical Shells
- Text-based shells (bash, zsh): Read text-shell-specific config files (
~/.bashrc,~/.zshrc) - Graphical shells: Read graphical-shell-specific config files
Common Combinations:
- Interactive Login: SSH sessions, console login
- Interactive Non-Login: Terminal applications in desktop environments
- Non-Interactive Non-Login: Script execution, automated tasks
You can check your shell's mode:
1 | # Check if login shell |
The Reality on macOS:
- Logging into macOS GUI: Graphical shell (doesn't read bash config, though)
- Opening iTerm2: Login command-line shell (reads shell config, i.e.,
~/.bash_profile,~/.zshrc, etc) - Additional iTerm2 windows: Also login shells (by default). This is because macOS Terminal apps are configured to start login shells.
Configuration Files
Source: Shell initialization files
Bash initialization order
According to man bash,
- Interactive login shell:
/etc/profile(system-wide)- First found:
~/.bash_profile,~/.bash_login,~/.profile(user-wide)
- Interactive non-login shell:
/etc/bash.bashrc(system-wide)~/.bashrc(user-wide)
Key files:
/etc/profile: Sources/etc/bash.bashrcand executes all/etc/profile.d/*.shscripts/etc/bash.bashrc: System-wide interactive settings (prompt, aliases)~/.bashrc: User interactive settings (Ubuntu default, contains history settings, aliases)
Platform differences:
- Ubuntu: Uses
~/.bashrcprimarily - macOS: Uses
~/.bash_profileprimarily - Missing files are skipped automatically
/etc/profile
Let's see it's content:
1 | In Ubuntu 22.04 |
Basically, /etc/profile will do two things:
Search if file
/etc/bash.bashrcexists, if it does, execute it.1
2
3if [ -f /etc/bash.bashrc ]; then
. /etc/bash.bashrc
fiSearch if dir
/etc/profile.dexists, if it does, execute all the files (if readable) in it.1
2
3
4
5
6
7
8if [ -d /etc/profile.d ]; then
for i in /etc/profile.d/*.sh; do
if [ -r $i ]; then
. $i
fi
done
unset i
fi
/etc/bash.bashrc
We can see the content of /etc/bash.bashrc :
1 | System-wide .bashrc file for interactive bash(1) shells. |
/etc/profile.d
We can see the content of files in /etc/profile.d :
1 | $ ls /etc/profile.d |
Every .sh file is just a simple shell script.
Zsh initialization order
--> Zsh manual
System-wide order: zshenv, zprofile, zshrc, zlogin User config: ~/.zshrc
Note: /etc/profile is only read by login shells, ~/.bashrc only by non-login shells. System creates the complete environment through this layered approach.
Hot to switch shell
Check availabe shells in the sysyem:
1 | cat /etc/shells |
Current shell:
1 | echo $SHELL |
set one shell as default for your user:
1 | chsh -s <full-path-to-shell> |
Shell and Syscall
由于系统调用fork()和exec()的分离,程序可以在fork()之后,exec()之前运行代码,方便了shell的工作
shell workflow
shell的workflow:- 显示一个
prompt,等待用户输入 - 用户进行输入,输入内容是一个命令,由一个可执行程序和若干参数组成
shell找到该可执行程序。调用fork()创建新进程shell可能执行某些代码- 调用
exec()执行这个可执行程序 - 调用
wait()等待该命令完成 - 子进程执行结束后,shell从
wait()返回,继续步骤1
- 显示一个
shell 重定向
shell实现重定向:对于wc 5_2.c > [filename].txt, wc的输出结果被重定向到[filename].txt
步骤解释:
shell在
fork()之后,exec()之前,会:- 先用
open打开文件[filename].txt, 给它分配一个文件描述符, 记为STDOUT_FILENO(一般是3,因为0,1,2都已被占用) - 再关闭标准输出( fd = 1 )
- 先用
shell使用
dup/dup2来分配一个STDOUT_FILENO的复制,由于dup默认是分配未使用的最小的fd),此时fd=1已经关闭,所以就又分配了fd=1,它是STDOUT_FILENO的复制shell执行指令,子进程准备输出,由于UNIX系统会从零开始寻找可用的fd(文件描述符),因此
STDOUT_FILENO会成为第一个可用的fd,作为子进程输出的目标
详见OS Persistence
- UNIX pipe也用类似方式实现,但使用
pipe()系统调用,将前一个进程的输入作为后一个进程的输出:grep -o foo fole | wc -l
Session 的创建和销毁
session的创建:
- 通常,新的 session 由系统登录程序创建,session 中的领头进程是运行用户登录 shell 的进程。新创建的每个进程都会属于一个进程组,当创建一个进程时,它和父进程在同一个进程组、session 中。
将进程放入不同 session 的惟一方法是使用 setsid 函数使其成为新 session 的领头进程。这还会将 session 领头进程放入一个新的进程组
session的销毁:
- 当 session 中的所有进程都结束时 session 也就消亡了。实际使用中比如网络断开了,session 肯定是要消亡的。
- 让 session 的领头进程退出。一般情况下 session 的领头进程是 shell 进程,如果它处于前台,我们可以使用 exit 命令或者是 ctrl + d 让它退出。或者我们可以直接通过 kill 命令杀死 session 的领头进程。
- 原理是:当系统检测到挂断(hangup)条件时,内核中的驱动会将 SIGHUP 信号发送到整个 session。通常情况下,这会杀死 session 中的所有进程
session 与终端的关系:
如果 session 关联的是伪终端,这个伪终端本身就是随着 session 的建立而创建的,session 结束,那么这个伪终端也会被销毁。
打开终端,会话开始;关闭终端,会话结束,会话内部的进程也会随之终止,不管有没有运行完。
一个典型的例子就是,SSH 登录远程计算机,打开一个远程终端执行命令。这时,网络突然断线,再次登录的时候,是找不回上一次执行的命令的。因为上一次 SSH 会话已经终止了,里面的进程也随之消失了
为了解决这个问题,会话与窗口可以"解绑":窗口关闭时,会话并不终止,而是继续运行,等到以后需要的时候,再让会话"绑定"其他窗口(见“ Terminal Multiplexer”)
如果 session 关联的是 tty1-6,tty 则不会被销毁。因为该终端设备是在系统初始化的时候创建的,并不是依赖该会话建立的,所以当 session 退出,tty 仍然存在。只是 init 系统在 session 结束后,会重启 getty 来监听这个 tty