Author:onceday Date:2022年9月12日
漫漫长路,有人对你微笑过嘛…
本文主要收集整理于以下文档:
整个Linux学习的终极目标就是编写自动化脚本,无论是文件、磁盘、进程、账户等,到了最后,不可能每一次都重新输入所有的命令。
这和编程的也不一样,编程的开发,不会去操心这么多的命令怎么用,编程也无法像脚本一样随写随用。从事实来说,编程和linux命令行是两种技能,熟练掌握linux命令行及其有利于学习和工作。
我们必须了解常见shell,即bash shell的语法知识,这将Linux学习的一个阶段性的里程碑。
用过的bash shell的人都会发现, shell会记录已使用过的命令情况, 但按下上、下方向键,便可以浏览历史命令,这功能十分好用,可以减少重新性的命令输入。
需要注意,当shell在运行的时候,历史命令缓存在内存中,在结束shell后,才会写入~/.bash_history中。因此,当bash shell非正常退出后,会发现丢失了此次的历史命令。
| 命令 | 描述 |
|---|---|
history | 查看最近使用过的命令列表 |
history -a | 强制将命令历史记录写入/etc/.bash_history中 |
history -n | 强制重新读取/etc/.bash_history中历史命令。 |
bash shell另一个好用的功能无疑是Tab补全,可以减少输入的复杂度,特别是文件名,减少了工作量。
在不同情况下,具有两种补全功能:
还有一个功能是命令别名Alias,由于每个命令的参数很多,输出形式也不一样,所以每个人都有自己喜欢的命令格式。Alias极大解决了这个问题,通过设置命令别名,可以搭建自己的命令集合。
alias ll='ls -al'
取消命令别名:
unalias ll
通配符可用来过滤文件或者输出的结果,拥有和正则表达式类似的结果。
许多命令都已被内置在bash中,因此有时候,我们想知道一个命令是否是内置命令、别名命令,还是外部命令,此时就需要使用type了。
type cmd_name #显示name命令是内置命令还是外部命令
type -t name #直接以file、alias、builtin三个关键字显示命令的类别
type -P name #如果后面的命令为外部命令才会显示完整的外部名
type -a name #显示PATH路径中所有的名为name的命令,包含alias
如果一行命令的参数太长,写不下,可以通过\转义Enter键,即:
onceday@ubuntu:~$ cp hello.c \
> hello2.c
再输入回车即可执行。一般命令行可以显示的列数都在80~100之间, 这受到诸多方面的限制。比如,我就不喜欢开一个特别大的命令行窗口,全屏输入命令这种行为我是一点都不喜欢的。
第二个就是快速操作已输入的字符串:
| 组合键 | 描述 |
|---|---|
| Ctrl+u | 从光标处向前删除整个字符串,用于快速删除前缀内容 |
| Ctrl+k | 从光标处向后删除整个字符串,用于快速删除后缀内容 |
| Ctrl+a | 让光标移动到整个命令串的最前面 |
| Ctrl+e | 让光标移动到整个命令串的最后面 |
当登入终端时,第一个启动shell就是父shell,后续启动的shell都算是其的子shell。要想观察这情况,需要使用ps -f命令,显示当前进程及子进程的具体情况。
onceday@ubuntu:~$ ps -f
UID PID PPID C STIME TTY TIME CMD
onceday 12 11 0 14:08 pts/0 00:00:00 -bash
onceday 85 12 0 14:45 pts/0 00:00:00 ps -f
可以看到在输入ps -f后,当前的shell进程会启动一个子进程,PPID是父进程ID,很明显,ps -f进程的父ID就是当前的bash shell进程。
这种父子shell进程的关系是可以嵌套的,如下:
onceday@ubuntu:~$ bash
onceday@ubuntu:~$ bash
onceday@ubuntu:~$ bash
onceday@ubuntu:~$ ps --forest
PID TTY TIME CMD
12 pts/0 00:00:00 bash
86 pts/0 00:00:00 \_ bash
92 pts/0 00:00:00 \_ bash
98 pts/0 00:00:00 \_ bash
104 pts/0 00:00:00 \_ ps
使用exit即可退出子进程,bash支持以下命令行参数:
| 参数 | 描述 |
|---|---|
| -c string | 从string中读取命令并进行处理 |
| -i | 启动一个能够接收用户输入的交互shell |
| -l | 以登入shell的形式启动 |
| -r | 启动一个受限shell,用户会被限制在默认目录中 |
| -s | 从标准输入中读取命令 |
通过使用;可以一次指定多条需要一次运行的命令。如下:
pwd; ls; cd /etc; pwd; cd; pwd; ls
这种形式下,每个命令依次执行,等待其他的命令执行完毕,然后执行自己的命令。
如果再加上括号(),则会将括号里命令放在一个子shell里面执行,即这些命令依次在子shell里面执行完毕。
上面直接使用括号包含命令,确实能够生成子shell执行,但此时父shell也被霸占,无法处理额外的命令。
直接使用命令后缀&符号,即可将一个命令放入后台运行。此时父shell还可以进行交互。
onceday@ubuntu:~$ sleep 20 &
[1] 125
onceday@ubuntu:~$ ps -f
UID PID PPID C STIME TTY TIME CMD
onceday 12 11 0 14:08 pts/0 00:00:00 -bash
onceday 125 12 0 15:17 pts/0 00:00:00 sleep 20
onceday 126 12 0 15:17 pts/0 00:00:00 ps -f
onceday@ubuntu:~$ jobs
[1]+ Running sleep 20 &
可以看到,返回的125即是其对应的PID,shell会创建一个子shell来执行后台命令。
也可以使用jobs来查看显示后台作业信息,可以显示当前运行在后台模式中所有用户的进程。
使用进程列表+后台模式,可以将子shell 放在后台运行:
(sleep 10; echo $BASH_SUBSHELL; sleep 10)&
使用coproc后接命令或者命令列表,即可创造协程,会在后台生成一个子shell,并在这个子shell中执行这个命令或者命令列表。
onceday@ubuntu:~$ coproc sleep 10;
[2]159
onceday@ubuntu:~$ jobs
[2]+ Running coproc COPROC sleep 10 &
默认协程会有一个名字COPROC,这个名字可以自定义,如下:
onceday@ubuntu:~$ coproc once { sleep 10; } [1] 153
当有多个协程运行,并且之间需要通信的时候,协程的名字才是有价值的。
需要注意,括号和命令之间,以及结尾的分号都是必须的,否则会报语法错误。
环境变量可以用来存储有关shell会话和工作环境的信息。
一般有两种:
以下是几种输出变量的方式:
| 命令 | 描述 |
|---|---|
| env | 输出所有全局变量 |
| printenv | 输出全部全局变量 |
| printenv HOME | 输出某个全局变量的值 |
| echo $HOME | 显示某个变量的值 |
| set | 输出全部变量(局部变量、全局变量、用户自定义变量)的值 |
在bash上,当一个变量没有值(尚未设置)时,其用echo显示会为空。但不同的shell上情况时不一样的。
直接使用var_name=var_value就可以创建局部变量。需要注意以下事项:
'x x',"x x"来书写。需要注意单引号是原始字符串,不会对里面的特殊字符进行操作,如$name,单引号不会替换值,而双引号会替换值。\转义字符,将Enter、$、\、空格、‘ 等变成一般字符${var_name}xxx变量的值是可以拼接的:
PATH=“$PATH":/home/bin
PATH=${PATH}:/home/bin
需要注意,系统环境变量的名字都是大写的,因此,为了避免不必要的冲突,用户自定义的变量最好是小写。
局部变量只在当前的shell中生效,比如在子shell中,就无法读取父shell中定义的局部变量的值。
设置全局变量很简单,使用export即可。
my_var="I am a variable"
export my_var
注意:虽然子shell可以继承父shell的全局变量,但是是两个不同的进程,因此子shell对全局变量的改变时无法传递到父shell,因为它们属于不同进程的全局变量。
删除全局变量使用unset就行:
unset my_var
注意这只对当前的shell进程生效。
设置和删除变量:
| 设置方式 | 说明 |
|---|---|
| ${变量#关键词} | 若变量从头开始的数据符合(关键词),则将符合的最短数据删除 |
| ${变量##关键词} | 若变量内容从头开始的数据符合(关键词),则将最符合的最长数据删除 |
| ${变量%关键词} | 若变量内容从尾向前的数据符合(关键词),则将最符合的最短数据删除 |
| ${变量%%关键词} | 若变量内容从尾部向前的数据符合(关键词),则将符合的最长数据删除 |
| ${变量/旧字符串/新字符串} | 若变量内容符合(旧字符串)则(第一个旧字符串会被新字符串替换) |
| ${变量//旧字符串/新字符串} | 若变量内容符合(旧字符串)则(全部的旧字符串会被新字符串替换) |
例如:
FILEPATH=/var/spool/mail/onceday
${FILEPATH##/*/}
#输出为onceday,即只保留文件名。
${FILEPATH%/*}
#输出为/var/spool/mail/,即保留路径,删除文件名
测试变量内容并设置(空字符串即var=“”):
| 变量设置方式 | str没有设置 | str为空字符串 | str已设置为非空字符串 |
|---|---|---|---|
| var=${str-expr} | var=expr | var=“” | var=$str |
| var=${str:-expr} | var=expr | var=expr | var=$str |
| var=${str+expr} | var=“” | var=expr | var=expr |
| var=${str:+expr} | var=“” | var=“” | var=expr |
| var=${str=expr} | str=expr, var=expr | str=“”, var=“” | str不变, var=$str |
| var=${str:=expr} | str=expr, var=expr | str=expr, var=expr | str不变, var=$str |
| var=${str?expr} | expr 输出至 stderr | var=“” | var=$str |
| var=${str:?expr} | expr 输出至 stderr | expr 输出至stderr | var=$str |
这些默认变量主要源自于Unix Bourne shell,下面只摘录了部分,详细可以通过man bash查看。
| 变量 | 描述 |
|---|---|
| CDPATH | 冒号分割的目录列表,用于cd命令的搜索路径 |
| HOME | 当前用户的主目录 |
| PATH | shell查找命令的目录列表,由冒号分割 |
| PS1 | shell命令行界面的主提示符 |
| PS2 | shell命令行界面的次提示符 |
| PS3 | select命令的提示符 |
| PS4 | 如果使用了bash的-x选项,在命令行之前显示的提示信息 |
| BASH | 当前shell的全路径名 |
| BASH_ENV | bash启动前尝试运行的脚本文件 |
| BASHPID | 当前bash进程的PID |
| COLUMNS | 当前bash shell实例所用终端的宽度 |
| FUNCNAME | 当前执行的shell函数的名称 |
| FUNCNEST | 设置非零值时,所允许的最大函数嵌套级数 |
| HISTFILE | 保存shell历史记录列表的文件名 |
| HISTFILESIZE | 最多在历史文件中存在多少行 |
| HISTSIZE | 最多在历史文件中存在多少条命令 |
| HOSTNAME | 当前主机的名称 |
| HOSTTYPE | 当前运行bash shell的机器 |
| LANG | shell的语言环境类别 |
| LC_ALL | 定义一个语言环境类别,能够覆盖LANG变量 |
| LINES | 定义了终端上可见的行数 |
| MACHTYPE | 用CPU-公司-系统格式定义的系统类型 |
| OLDPWD | shell之前的工作目录 |
| OSTYPE | 定义了shell所在的操作系统 |
| PPID | shell父进程的ID |
| PWD | 当前工作目录 |
| RANDOM | 返回一个0~32767的随机数,可赋值成为随机数种子 |
| SECONDS | 自从shell启动到现在的秒数(对其赋值将会重置计数器) |
| SHELL | bash shell的全路径名 |
| SHLVL | shell的层级,每次启动一个新bash shell,该值增加1 |
| TIMEFORMAT | 指定了shell的时间显示格式 |
| TMOUT | select和read命令在没输入的情况下等待多久,默认值为0,表示无限长。 |
| TMPDIR | 目录名,保存bash shell创建的临时文件 |
| UID | 当前用户的真实用户ID |
这个就是命令行在输入命令之前出现的提示符内容:
onceday@ubuntu:~$ echo $PS1
\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$
其中掺杂的数字字符,是用来控制显示颜色的,而\e等这些, 是一些特殊的符号,下面是它们的含义:
| 符号 | 含义 |
|---|---|
| \d | 显示出日期的格式,[Mon Feb 2] |
| \H | 完整的主机名 |
| \h | 仅取主机名在第一个小数点之前的名字 |
| \t | 显示时间,为24小时格式: HH:MM:SS |
| \T | 显示时间,为24小时格式: HH:MM:SS |
| \A | 显示时间,为24小时格式: HH:MM |
| @ | 显示时间,为12小时格式: am/pm |
| \u | 目前用户的账号名称 |
| \v | BASH的版本信息 |
| \w | 完整的工作目录 |
| \W | 利用basename函数取得工作目录的名称,仅会列出最后一个目录 |
\# | 执行的第几个命令 |
\$ | 提示字符,如果是root,提示字符为#,否则就是$ |
色彩控制符的格式如下:\[\033[01;32m\]\u@\h\[\033[00m\]
显示方式:\033[显示方式;前景色;背景色 + m + 正文 + \033[0m.
| 显示方式 | 意义 |
|---|---|
| 0 | 终端默认设置 |
| 1 | 高亮显示 |
| 4 | 使用下划线 |
| 5 | 闪烁 |
| 7 | 反白显示 |
| 8 | 不可见 |
下面是色彩的控制代码:
| 前景色 | 背景色 | 颜色 |
|---|---|---|
| 30 | 40 | 黑色 |
| 31 | 41 | 红色 |
| 32 | 42 | 绿色 |
| 33 | 43 | 黃色 |
| 34 | 44 | 蓝色 |
| 35 | 45 | 紫红色 |
| 36 | 46 | 青蓝色 |
| 37 | 47 | 白色 |
shell有三种启动方式:
默认登入shell会从5个不同的启动文件里读取命令:
/etc/profile$HOME/.bash_profile$HOME/.bashrc$HOME/.bash_login$HOME/.profile如果使用可拆卸认证模块,Pluggable Authentication Modules, PAM。那么还可能读入以下两个文件:
/etc/environment$HOME/.pam_environment显然,/etc/profile是所有用户共享的启动文件。在这个文件里,一般会遍历一个启动脚本的文件夹:
/etc/profile.d/可以把需要启动就运行的脚本放在这些文件夹里面。
需要注意的是,后面三个HOME里面的启动文件,并不会同时都加载,具体需要看/etc/profile里面的代码。
非登入时启动的交互式shell,如在bash里面启动的shell。
这样的交互式shell不会访问/etc/profile文件,但是会检查$HOME/.bashrc文件。
系统运行脚本,或者编程运行脚本就是这样。
这种shell可以通过BASH_ENV环境变量来检查是否有要执行的启动文件。
自定义的变量可以存放于/etc/profile里面,但是如果系统更新或者版本更新,那么这个文件很有可能被重置。因此,最好把文件放在/etc/profile.d上,这样不容易被覆盖。
在/etc/issue可以填写tty1~tty6的登入欢迎信息。
onceday@ubuntu:~$ cat /etc/issue
Ubuntu 20.04.3 LTS \n \l
\n和\l是变量,具体信息可以在man issue里面查看。
| 变量符号 | 描述 |
|---|---|
| \d | 本地端时间的日期 |
| \l | 显示第几个终端界面 |
| \m | 显示硬件的等级 |
| \n | 显示主机的网络名称 |
| \O | 显示domain name |
| \r | 操作系统的版本,相当于uname -r |
| \t | 显示本地端时间的时间 |
| \S | 操作系统的名称 |
| \v | 操作系统的版本 |
使用stty -a可以列出目前环境的所有按键列表。
默认的按键列表如下:
| 组合按键 | 执行结果 |
|---|---|
| Ctrl+C | 终止目前的命令 |
| Ctrl+D | 输入结束(EOF),例如邮件结束的时候 |
| Ctrl+M | 回车 |
| Ctrl+S | 暂停屏幕的输出 |
| Ctrl+Q | 恢复屏幕的输出 |
| Ctrl+U | 在提示字符下,将整列命令删除 |
| Ctrl+Z | 暂停目前的命令 |
| 符号 | 意义 |
|---|---|
| * | 代表0到无穷多个任意字符 |
| ? | 代表至少有一个任意字符 |
| [ ] | 同样代表一定有一个在中括号内的字符,如[abc]代表a,b,c中任意一个 |
| [ - ] | 在顺序编码内的所有字符,如[a-z]代表所有小写字母 |
| [^] | ^代表反向选择,[^a-b]代表非小写字母的任意一个字符 |
示例:
#找出以corn开头的文件名
ll -d /etc/cron*
#找出刚好是五个字符的文件名
ll -d /etc/?????
#找出含有数字的文件名
ll -d /etc/*[0-9]*
| 符号 | 内容 |
|---|---|
| # | 注释符号 |
| \ | 转义符号,将特殊符号和通配符还原成一般字符 |
| | | 管道(pipe):分割两个管道命令的符号 |
| ; | 连续命令执行分隔符:连续性命令的界定 |
| ~ | 用户的家目录 |
| $ | 使用变量前导符,亦即是变量之前需要加的变量替换值 |
| & | 任务管理(job control):将命令变成后台任务 |
| ! | 逻辑运算意义上的非not的意思 |
| / | 目录符号:路径分割的符号 |
| >,>> | 数据流重定向,>是替换,>>是叠加 |
| <,<< | 数据流重定向,输入定向 |
| ’ ’ | 单引号,不具有变量替换的功能 |
| “ ” | 具有变量替换的功能 |
| `` | 两个分词符中间为可以先执行的命令,也可以使用$( ) |
| ( ) | 在中间为子shell的起始与结束 |
| { } | 在中间为命令区块的组合 |