Bash工作手册
本文主要讲述bash的配置,各种命令(包含内置命令以及类似内置命令的指令),运行原理等主题
Bash配置
自动补全
$ type complete
complete is a shell builtin
https://juejin.cn/post/6844904096411942926
https://jasonkayzk.github.io/2020/12/06/Bash命令自动补全的原理/
系统自带的命令补全功能有限,自动补全功能仅限于命令和文件名。可以安装 Bash 命令补全增强软件包 bash-completion来实现更多命令的补全。
CentOS 默认会安装一个 bash-completion 包,这里面包含了常用命令的大部分自动补齐脚本,在编写脚本时可以直接参考这个包里的内容;
很多特有命令的自动补全支持不在bash-completion内,这时候可以手动添加进去。 比如git、docker等经常使用的命令。
安装bash-completion之后,一般会生成一个bash_completion.d的目录, 这个目录下的配置会被bash_completion加载,所以不用配置,只需要把自己的配置脚本放到这个目录下!
git安装之后文档里会有git-completion.bash文件, 移动到里面bash-completion目录里就行了。
mv git-completion.bash /etc/bash_completion.d
bash的特殊变量
| 变量 | 描述 |
|---|---|
$0 |
当前脚本的文件名。 |
$n(n≥1) |
传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是 $1,第二个参数是 $2。 |
$# |
传递给脚本或函数的参数个数。 |
$* |
传递给脚本或函数的所有参数。 |
$@ |
传递给脚本或函数的所有参数。当被双引号" "包含时,$@ 与 $* 稍有不同,我们将在《Shell $*和$@的区别》一节中详细讲解。 |
$? |
上个命令的退出状态,或函数的返回值,我们将在《Shell $?》一节中详细讲解。 |
$$ |
当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID。 |
特殊环境变量
PATHPATH 变量是由 Bash shell 程序维护和使用的环境变量,存储了一组以冒号分隔的目录路径,用于查找可执行程序的位置。LANGLC_*PROMPT_COMMAND这个变量的中内容是作为一个普通的bash命令执行的,执行时机是在bash显示prompt之前。TERM当前终端
更改命令提示符
PS1='\[\e[32m\]\u@\h:\w \$\[\e[0m\] '
bash的初始化
passwd 文件中指定了用户登录后的默认 shell。
export LD_LIBRARY_PATH=/usr/local/lib
/etc/profile此文件为系统的每个用户设置环境信息, 当用户第一次登录时,该文件被执行.并从/etc/profile.d目录的配置文件中搜集shell的设置. 是全局的。/etc/bashrc是shell 全局自定义配置文件,主要用于自定义 shell,该配置对所有用户的shell都生效;/root/.bashrc用于单独自定义root用户的 bash,只对root用户的bash生效,如果要使elk用户生效,则需要配置/home/elk/.bashrc文件;/root/.bash_profile用于单独自定义root用户的系统环境,只对root用户生效。~/.bash_profile是home目录主人即当前用户特有有环境配置。会调用~/.bashrc
加载顺序:
要验证顺序,打印出来即可,在四个文件末位追加echo命令就可以验证 echo "echo this is xx" >> xx
- 首先读取
/etc/profile文件,该文件对于所有的登录用户都会执行。该文件定义全局的环境变量和启动程序,例如系统的 PATH 变量等。 - 然后读取
/etc/bashrc文件,该文件也对所有的登录用户都会执行。该文件定义全局的 Bash Shell 的特定的设置,例如 Bash 的别名,颜色输出等。 - 当某个用户登录时,系统会读取该用户的家目录下的
.bash_profile文件,如果该文件存在的话。该文件主要用来定义用户特定的环境变量和启动程序等。 - 如果
.bash_profile文件不存在,则会读取用户的家目录下的.bashrc文件。.bashrc文件也定义了用户特定的环境变量和启动程序等,但是和.bash_profile不同的是,.bashrc文件对于交互式的和非交互式的 Shell 都会执行。
总的来说,/etc/profile 和 /etc/bashrc 文件定义了全局的环境变量和启动程序,而 /root/.bash_profile 和 /root/.bashrc 文件定义了特定用户的环境变量和启动程序。对于每个用户的 Bash Shell,系统会首先读取 /etc/profile 和 /etc/bashrc 文件,然后读取该用户的家目录下的 .bash_profile 文件,最后读取 .bashrc 文件。

.bashrc 中是自动执行的初始化脚本,建议将export PATH=... alias=...写到此处。
https://www.cnblogs.com/liang-io/p/9825363.html
shell的内置命令
type命令
type type
type alias //
type cd
type pwd
type echo
type bg
type fg
type jobs
type export //环境变量导出
type ulimit //资源限定
type umask //同上
type history //历史
type source //bash内执行脚本
type exit //退出
type passwd
type ll
type touch
type rm
type ps
type reset
type chroot //change root directory
type命令会返回一个命令的性质:
- 是shell内置命令
- 还是exe命令
- 别名
内置命令是shell内部调用的集成在shell中的,执行时可能改变shell这个进程内部状态。比如cd命令会改变进程的“当前目录”,这是一个进程的属性在内核中有数据结构表示“当前目录”,cd会改变这个。再比如ulimit umask也是设定shell进程某些属性的。
而exe是.sh脚本或者elf可执行文件(不区分两者因为对shell来说无逻辑上区别)。
alias 别名
alias mstat='cat /proc/meminfo'
alias的效力仅及于该次登入的操作。若要每次登入是即自动设好别名,可在.profile或.cshrc中设定指令的别名。
若仅输入alias 则可列出目前所有的别名设置。
echo 回声
echo 本没什么好说,放这是为了说明 * 通配符。*/~的解释是shell做的, 操作系统内核是不认*/~的!!!
echo *
echo *.exe
echo ~
匹配所有目录下的.exe文件。echo可以用来测试参数输入是否是预期的
根据实验,shell是根据$HOME变量解释~的。
$ export HOME=/root
$ echo ~
/root
reset
reset 通过向终端写控制字符来调整输出格式。
//strace -o strace.txt reset
//cat strace.txt
...C
ioctl(2, TIOCGWINSZ, {ws_row=58, ws_col=237, ws_xpixel=0, ws_ypixel=0}) = 0
ioctl(2, SNDCTL_TMR_STOP or SNDRV_TIMER_IOCTL_GINFO or TCSETSW, {B38400 -opost isig icanon echo ...}) = 0
write(2, "\r", 1) = 1
write(2, "\33", 1) = 1
write(2, "[", 1) = 1
write(2, "3", 1) = 1
write(2, "g", 1) = 1
int main() {
printf("This is a \033[1;35m test \033[0m!\n");
printf("This is a \033[1;32;43m test \033[0m!\n");
printf("\033[1;33;44mThis is a test !\033[0m'\n");
return 0;
}
export 环境变量
bash 变量与环境变量:变量不能遗传,环境变量可以。
bash 内部也有一些变量影响行为 echo $PS1
export 导出
PATH=/usr/new:$PATH
export PATH
export VAR=blahblahblah
export 'CLICKHOUSE_EXAMPLES_CLICKHOUSE_API_ENV'='{"Port":9000,"Host":"127.0.0.1","Username":"default","Password":"","Database":"tutorial"}'
注意变量名有特殊字符时需要用'' ""引起来,不然会被bash转义掉。
PATH 是shell找可执行命令或者脚本的搜索目录。:隔开的每项路径中依次查找,先找到的被执行。
set&unset 设置
set显示环境变量
unset删除环境变量
特殊字符
globs * 是由bash解释的。 意思是比如cat *.txt 其实bash会将~转成a.txt b.txt传给cat,cat收到的不会是*.txt。
| character | name | Uses |
|---|---|---|
| * | star | 正则,glob |
| . | dot | 当前目录, 扩展名 |
| ! | bang | 取反,history |
| | | pipe | 命令管线 |
| / | slash | 目录分隔符, 搜索命令(例如vim) |
| \ | backslash | 字面值,转义引导 |
| $ | dollar | 变量取值,行末 |
| ' | tick, quote | 字符串字面值 |
| ` | backtick, backquote | 命令替换 |
| " | double dollar | 半字面值 |
| ^ | caret | 取反,行首 |
| ~ | tilde | home目录 |
| # | sharp | 注释,预处理,替换 |
| [] | brackets | 范围 |
| {} | braces | 语句块 |
| _ | underscore | ... |
env命令
env命令是个elf文件,原理是利用bash运行新命令env时会调用execv("env", ${oldenv})将环境变量传递下去, 然后env调用api获取env。
chroot命令
在 linux 系统中,系统默认的目录结构都是以 /,即以根 (root) 开始的。而在使用 chroot 之后,系统的目录结构将以指定的位置作为 / 位置。
chroot NEWROOT [COMMAND [ARG]...]
chroot是一个可执行程序 /usr/sbin/chroot 原以为是一个built-in命令,结果不是。这个命令需要在
chroot的基本逻辑如下:
int main(int argc, char *argv[])
{
if(argc<2){ printf("Usage: chroot NEWROOT [COMMAND...] \n"); return 1; }
if(chroot(argv[1])) { perror("chroot"); return 1; }
if(chdir("/")) { perror("chdir"); return 1; }
if(argc == 2) {
argv[0] = (char *)"/bin/sh";
argv[1] = (char *) "-i";
argv[2] = NULL;
} else {
argv += 2;
}
execvp (argv[0], argv);
printf("chroot: cannot run command `%s`\n", *argv);
return 0;
}
用处:
- 增加了系统的安全性,限制了用户的权力
- 建立一个与原系统隔离的系统目录结构,方便用户的开发
- 切换系统的根目录位置,引导 Linux 系统启动以及急救系统等
用例
- 安装archlinux时,装完之后,需要用chroot切换到新安装的mnt下,以继续
- 通过 chroot 重新设置 root 密码
chroot总结
chroot 是一个很有意思的命令,我们可以用它来简单的实现文件系统的隔离。但在一个容器技术繁荣的时代,用 chroot 来进行资源的隔离实在是 low 了点。所以 chroot 的主要用途还是集中在系统救援、维护等一些特殊的场景中。
source命令(当前shell下解释执行)
source是bash的内置命令,source命令主要是读取文件信息,然后执行里面的脚本。与sh xxx/bash xxx的区别是,这个内置命令是在当前bash进程内解释执行本脚本的,不是新启动子bash进程中执行脚本的。
source还有一个等效的.命令,也是同样的效果。. my-script.sh 等价于 source my-script.sh
实验,例如有个脚本:
# demo.sh
export ABC=12345
sh demo.shenv | grep ABC发现没有ABC环境变量source demo.shenv | grep ABC发现ABC环境变量已经定义exit之后 重新登录 ABC环境变量消失
常见用法:
source /etc/profile
etc/profile是一个sh文件,主要用于配置环境。
比如导入一些路径:
export PATH:=$PATH:/usr/local/path/to
man 命令
man -k keyword 搜索手册中关键字。如果不知道准确名字就用关键字搜。
man SECTION PAGE man 可以查询不同类型的帮助手册,类型SECTION如下
- 可执行程序或 Shell 命令
1p. 可执行程序或 Shell 命令(POSIX 版) - 系统调用(内核提供的函数)
- 库调用(程序库中的函数)
- 特殊文件(通常在/dev中找到)
- 文件格式和约定,如 /etc/passwd
- 游戏
- 杂项(包括宏包和约定),例如 man(7)、groff(7)
- 系统管理命令(通常只针对 root 用户)
- 内核相关文件[非标准]
man 2 read查看系统调用 read 的帮助手册。man 3 printf查看库函数 printf 的帮助手册。man 4 tty查看特殊的设备文件 tty 的帮助手册。
sh命令参数
man sh GNU Bourne-Again SHell
-iinteractive 模式,就是执行完命令之后 进入交互模式
Bash 运行逻辑
继承
fork 会继承句柄表 信号表等
比如strace会fork+exec. strace启动的子进程会继承stdout
基本逻辑
shell执行exe的一般逻辑是 fork+exec+wait4
shell 会将> >> | & 特殊对待
>会导致 open+dup2。 >之后的参数会被shell认为是文件名 这部分逻辑是shell干的。
比如 nohup java -jar > test.txt springboot.jar 首先shell 识别 > test.txt ,单独摘出来形成nohup java -jar springboot.jar 然后将剩余的参数传递给exe。 从实验来看, 管线符|是优先最高的,首先做的事情是将|分隔的各命令区分开。然后依次解析各个隔开的子命令。
实验
package main
import "fmt"
import "os"
func main() {
fmt.Println(os.Args);
}
将以上go代码编译成 ./cmdparser
[pxfgod@VM-188-255-centos ~/test]$ ./cmdparser -a > redirect.txt -b -c
[pxfgod@VM-188-255-centos ~/test]$ cat redirect.txt
[./cmdparser -a -b -c]
bash主进程fork + wait4, 子进程 open("test.txt", O_RD) + dup2()调用。如此子进程的stdout就是 test.txt
子进程再以nohup java -jar springboot.jar 执行nohup。 java -jar springboot.jar都是nohup的argv由nohup解释。
|会运行多个进程。会调用pipe
& 就是简单的不要wait4。
重定向与管线
> 重定向输出到新文件 >> 重定向且append模式到文件。2> 重定向标准错误。
$ command > file
$ command >> file
$ head < /proc/cpuinfo
遇到cmdA | cmdB bash会:
- bash
fork出新进程记为X - 父进程bash
wait4X X调用pipe创建通信的管道:他们是双生子 一个的输出pout是另一个的输入pinX调用fork出Y此时X Y有相同的文件句柄表X调用dup2pin置换掉stdin 并且close掉pin/poutY调用dup2pout置换掉stout 并且close掉pout/poutX调用execv执行cmdBY调用execv执行cmdA
shell实现重定向的原理
在I/O重定向的过程中
- 不变的是FD 0/1/2代表STDIN/STDOUT/STDERR
- 变化的是文件描述符表中FD 0/1/2对应的具体文件,这是通过系统调用
dup2()做到的。
重定向命令> >> 由 open() + dup2() 实现
管道命令| 由fork() + pipe() + dup2() 实现
dup2()偷天换日,把fd索引的内核层的文件描述符偷偷替换掉。
int main() {
int pid = 0;
// fork a worker process
if (pid = fork()) {
// wait for completion of the child process
int status;
waitpid(pid, &status, 0);
}
else {
// open input and output files
int fd_in = open("in.txt", O_RDONLY);
int fd_out = open("out.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (fd_in > 0 && fd_out > 0) {
// redirect STDIN/STDOUT for this process
dup2(fd_in, 0);
dup2(fd_out, 1);
// call shell command
system("sort");
close(fd_in);
close(fd_out);
}
else {
// ... error handling
}
}
return 0;
}
{
//parse > <
//if > out.txt
// open out.txt
//fork()
// dup2
// 子进程 exec
for each cmd {
k = fork();
if (k == 0) {
dup2
}
wait4
}
}
fork+exec+wait4 && EXPORT 环境变量
execve 可以设定新程序的环境变量。
exec 系列函数只清除一些特定属性。 比如地址空间里的程序、数据, mmap出来的之类的都被清除。大部分属性保留,比如:文件句柄表就是保留的。
参见 https://man7.org/linux/man-pages/man2/execve.2.html
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("PID=%d\n", getpid());
char *newargv[] = { NULL, "hello", "world", NULL };
char *newenviron[] = { NULL };
if (argc != 2) {
fprintf(stderr, "Usage: %s <file-to-exec>\n", argv[0]);
exit(EXIT_FAILURE);
}
newargv[0] = argv[1];
execve(argv[1], newargv, newenviron);
perror("execve"); /* execve() returns only on error */
exit(EXIT_FAILURE);
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
printf("PID=%d\n", getpid());
for (int j = 0; j < argc; j++)
printf("argv[%d]: %s\n", j, argv[j]);
exit(EXIT_SUCCESS);
}
[pxfgod@VM-113-33-centos ~]$ ll /proc/24012/fd
total 0
lrwx------ 1 pxfgod pxfgod 64 Feb 16 15:22 0 -> /dev/pts/0
lrwx------ 1 pxfgod pxfgod 64 Feb 16 15:22 1 -> /dev/pts/0
lrwx------ 1 pxfgod pxfgod 64 Feb 16 15:22 2 -> /dev/pts/0
lr-x------ 1 pxfgod pxfgod 64 Feb 16 15:22 3 -> pipe:[8362837]
l-wx------ 1 pxfgod pxfgod 64 Feb 16 15:22 4 -> pipe:[8362837]
可以看到pipe被保留了。
我们来看一下top | grep管线命令下两进程的文件列表就一目了然了。
[pxfgod@VM-113-33-centos ~]$ ll /proc/25914/fd
total 0
lrwx------ 1 pxfgod pxfgod 64 Feb 16 15:35 0 -> /dev/pts/0
l-wx------ 1 pxfgod pxfgod 64 Feb 16 15:35 1 -> pipe:[8341741]
l-wx------ 1 pxfgod pxfgod 64 Feb 16 15:35 2 -> /dev/null
lrwx------ 1 pxfgod pxfgod 64 Feb 16 15:35 3 -> /dev/pts/0
lr-x------ 1 pxfgod pxfgod 64 Feb 16 15:35 4 -> /proc/uptime
lr-x------ 1 pxfgod pxfgod 64 Feb 16 15:35 5 -> /proc/meminfo
lr-x------ 1 pxfgod pxfgod 64 Feb 16 15:35 6 -> /proc/loadavg
lr-x------ 1 pxfgod pxfgod 64 Feb 16 15:35 7 -> /proc/stat
[pxfgod@VM-113-33-centos ~]$ ll /proc/25915/fd
total 0
lr-x------ 1 pxfgod pxfgod 64 Feb 16 15:35 0 -> pipe:[8341741]
lrwx------ 1 pxfgod pxfgod 64 Feb 16 15:35 1 -> /dev/pts/0
lrwx------ 1 pxfgod pxfgod 64 Feb 16 15:35 2 -> /dev/pts/0
可看到top的标准输出被pipe到了grep的标准输入。
bash的``
`引起来的内容` 是命令的stdout输出内容,将被解释为bash的命令的一部分
cat /proc/`pgrep top|grep -v grep`/status
bash的 && ||
短路性质
References
https://explainshell.com/ 这个解析bash命令的工具非常有用

浙公网安备 33010602011771号