本文主要讲述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。 |
特殊环境变量
PATH
PATH 变量是由 Bash shell 程序维护和使用的环境变量,存储了一组以冒号分隔的目录路径,用于查找可执行程序的位置。LANG
LC_*
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.sh
env | grep ABC
发现没有ABC环境变量source demo.sh
env | 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
-i
interactive 模式,就是执行完命令之后 进入交互模式
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
wait4
X
X
调用pipe
创建通信的管道:他们是双生子 一个的输出pout是另一个的输入pinX
调用fork
出Y
此时X Y有相同的文件句柄表X
调用dup2
pin置换掉stdin 并且close掉pin/poutY
调用dup2
pout置换掉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命令的工具非常有用
标签:文件,shell,pxfgod,命令,工作手册,Bash,type,bash From: https://www.cnblogs.com/pxfgod/p/17310950.html