Learn Shell

2022/8/5

# 学习 Shell 脚本

# 基础概念

  1. 如果提示符的最后一个字符是#, 而不是$, 那么这个终端会话就有超级用户权限。 这意味着,我们要么是以 root 用户的身份登录,要么是我们选择的终端仿真器提供超级用户(管理员)权限。

  2. Shell 脚本的解释过程就是从文件读入字符流,然后进行处理,最后将结果传送至某个文件,所以交互式 Shell 命令与 Shell 脚本在本质上并没有区别,只是 Shell 命令的输入是标准输入,输出是标准输出。

  3. Shell 脚本的注释以#符号开始,一直到行末结束,例如可以在 Shell 命令行中输入以#开头的命令,则该命令将会被作为注释而忽略处理。

  4. Sha-Bang 是 Shell 脚本开头字符#!连在一起的读音,当 Shell 文件被 Linux 系统读取时,内核会通过#!表示的值(0x23, 0x21)识别出随后的解释器路径并调用,最后再将整个脚本内容传递给解释器。由于 Shell 当中#字符同时表示注释,因此 Shell 解释脚本文件时会自动忽略该行。

# 在线 Shell 教程&语句解释工具

  1. explainshell (opens new window)
  2. Shell 程序设计教程 (opens new window)
  3. Shell 教程 (opens new window)

# 执行方式

Shell 脚本的执行主要存在如下 5 种方式:

  1. 拥有执行权限的脚本作为命令调用,例如:./hello.sh
  2. 显式使用 Shell 程序,将脚本文件作为参数来执行,例如:sh hello.sh
  3. 将脚本文件重定向至 Shell 程序的标准输入,例如:sh < hello.sh
  4. 通过管道符将脚本内容输出至 Shell 程序的标准输入,例如:cat hello.sh | sh
  5. 使用 source 命令执行,例如:source hello.sh

# 字符串与引号

Shell 解释器采用了字符流过滤器模型,简而言之,就是一条命令将结果送到标准输出,这个标准输出被连接到下一条命令的标准输入,每条命令的输出结果都是自己处理之后的字符流,接受的输入都是需要进行处理的字符流,所以字符串是 Shell 当中非常重要的组成部分。

Shell 当中存在'、"、`三种引号类型,其具体使用区别分别如下所示:

  1. 单引号'当中的字符串 Shell 不会进行处理,仅在需要保持字符串原样不变的时候使用;
  2. 双引号"当中的字符串 Shell 会进行处理,如果其中包含有可以求值的部分(变量、表达式、命令),则会被 Shell 替换为相应的求值结果;
  3. 反引号`用于引用一条 Linux 命令,其作用是将该命令的执行结果输出,效果类似于$()

# 特殊字符

  1. *?都是通配符,前者匹配任意个字符,后者仅匹配一个字符;*.js表示任意文件名,**/*.js表示任意一层子目录。
  2. :表示空命令,其返回值恒为 0,循环语句当中,可以与 true 命令等价;
  3. ;是分行符,标识一行命令结束,可以通过它将多条命令编写在一行;
  4. $可以用于获取变量或者表达式的值,结合大括号${}使用,可以在变量出现在字符串当中时,不与字符串内容相混淆;结合小括号$()可以取一个命令的值作为字符串内容,其效果与反引号`相同;通过双小括号$(())可以取得一个数学表达式的计算结果,例如在使用*运算符计算一个乘积;
  5. .句点符号,等效于 source 命令,可用于在 Shell 进程上调用脚本;
  6. \反斜线表示转义符,是一种引用单个字符的方法,也可以用于 Shell 命令的换行;
  7. 空格作为参数命令的做分隔符,例如:touch a b会创建 a 和 b 两个文件,而touch c\ d则只会创建一个名为'c d'文件;
  8. 执行脚本时&符号表示是并行执行,&&表示是继发执行(即只有前一个任务成功,才执行下一个任务)。

# 内/外部命令

  1. 外部命令:Shell 的绝大多数命令如同/bin/ls 一样,是一个独立的外部可执行程序。当外部命令被调用时,本质就是调用了另外一个程序,首先 Shell 会创建子进程,然后在子进程当中运行该程序;
  2. 内部命令:内建在 Shell 程序当中,由 Shell 软件内部进行实现的命令,例如:cd、source、export、time 等,它们都运行在 Shell 进程当中。
  3. 注意:如果希望脚本能够改变当前 Shell 自身的一些属性,则必须在 Shell 进程内执行调用。例如修改/etc/profile、~/.profile、~/.bashrc环境变量之后,必须使用 source 命令执行它们,以使其生效。

# 重定向

  1. Shell 的设计哲学是字符流 + 过滤器,即将一个程序的输出,作为另一个程序的输入,这样就能将各种用途简单的小工具组合起来,完成一些看起来不可思议的功能。

  2. 默认情况下,Linux 当中的每一个进程都拥有 3 个特殊的文件描述指针:

  • 标准输入 stdin:Standard Input,文件描述指针为 0;
  • 标准输出 stdout:Standard Output,文件描述指针为 1;
  • 标准错误输出 stderr:Standard Eror,文件描述指针为 2;
  1. IO 重定向就是捕捉命令、程序、脚本甚至代码块的输出,然后将其作为输入传递给另外的文件、命令、程序、脚本。

# 输出重定向

输出重定向符号>>>,可以将标准输出重定向至一个文件当中,如果该文件不存在则创建文件。其中,前者>会覆盖原文件内容,后者>>则会在原文件尾部追加新的内容。

# 输入重定向

输出重定向符号<<<,用于将标准输入重定向至一个文件。如果<后跟着一个 Shell 脚本文件,则相当于将.sh脚本中的命令逐条输入至 Shell 程序当中执行。

<<可以用于 Here Document, 即将文本直接写在 Shell 脚本之中,并以添加终止符EOF(即 Linux 系统读取至文件结尾时所返回的信号值-1),该文本相当于一份独立的文件内容。

执行下面的脚本,会先打印两行 string,然后打印当前目录下的文件 list,然后生成一个 hello.js 文件并写入一行代码,然后执行这个 js 文件,打印出 string,最后列出当前目录下的文件 list 并删除 hello.js 文件。

echo Hello World
echo Second Hello World
ls -l

cat>hello.js<<EOF

console.log("Hi, this is js");

EOF

node hello.js

ls -l

rm hello.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

注意:Here Document 通常用于进行复杂的多行文本输入时,从而代替 echo 命令繁琐的硬编码操作。

# 管道

管道符|用于连接 Linux 命令,前一条命令的标准输出会成为下一条命令的标准输入。管道的最大特点在于是管道符|两边分别属于不同的进程。

例如:从 dmesg 输出的内核日志信息中,通过 grep 查找 USB 相关的内容。$ dmesg | grep USB

# 常量与变量

  1. Shell 支持多种进制的整型常量,例如以0开头的八进制,以0x开头的十六进制。对于非八进制、十进制、十六进制的整数,可以表示为进制#数字格式,例如:三进制数(120)₃可以表示为3#120,转换为十进制值为 15。

  2. Shell 中的变量在使用前不需要声明,赋值时可以直接使用变量名,且赋值的等号=两边不能有空格。变量定义之后,引用变量时一定需要使用$/${}符号。

  3. Shell 变量没有类型,例如 annum=2020,既可以作为十进制整数 2020 直接参与算术运算,也可以作为字符串来进行处理。

  • 使用 let 计算一个算术表达式并且赋值给变量:let "annum+=1"
  • 将字符串变量中的 202 替换成为 203:b=${annum/202/203}; echo b; # 2031
  1. Shell 变量有作用域,默认为对整个 Shell 文件有效的全局变量。局部变量则需要使用 local 关键字进行声明,其只在声明所在的块或者函数当中可见。
#!/bin/bash
# test.sh
test() {
    variable1=GLOBAL
    local variable2=LOCAL
    echo "函数内部,variable1=$variable1, variable2=$variable2" # 两个都能打印出来
}

test # 执行一下test函数

echo "函数外部,variable1=$variable1, variable2=$variable2" # 只能打印variable1
1
2
3
4
5
6
7
8
9
10
11
  1. ?问号也是一个变量,通过$?可以引用上一条命令的返回值,但是该值只能使用一次,使用完以后就会被目前命令的返回值所替换。
  • false 命令返的回值恒为 1
  • echo 命令的返回值为 0

# 环境变量

环境变量是可以改变 Shell 行为的变量,每个进程都拥有各自的环境变量,以用于保存进程相关的各种信息。环境变量的定义通常都是约定俗成的,例如:PATH 定义了 Shell 进程查找命令程序的路径。echo $PATH

Shell 当中的任何变量都可以通过 export 导出为环境变量,环境变量可以被子进程继承,因此也可以被视为父子进程信息传递的一种方式。

$ export PATH="$PATH:/workspace"可以给 PATH 后面添加:/workspace的环境变量。

# 位置参数

位置参数是指调用 Shell 脚本时,按照命令行位置进行引用的参数。脚本当中按照$0$1$2的顺序逐个进行引用,依此类推。其中$0就代表命令本身。

命令行参数相关的特殊变量还有$#$*$@,其使用方法如下所示:

  • $#:代表命令行参数的个数;
  • $*:代表全部命令行参数,全部参数作为一个字符串;
  • $@:代表所有命令行参数,每个参数都是一个独立的字符串;

# 操作符

Shell 当中的每一条命令同时也是一个逻辑表达式,其返回值为 0 表示真,返回值为非 0 表示假,该值本质上就是当前命令所对应 main()函数的返回值,可以通过$?来进行获取。Shell 支持基本的数学运算符号以及各种逻辑操作符。

  1. 数学运算符包括+、-、*、/、%以及幂运算**,Bash Shell 本身只支持整数运算,如果需要使用到浮点运算,则可以调用bcdc等外部命令。
  2. 逻辑操作符包括&&和||,分别代表逻辑与和逻辑或。对于逻辑与&&而言,如果左侧表达式为 false,则右侧表达式无需执行(实际也不会执行)即可确定整个表达式的结果为 false;

# 脚本返回值

通常情况下,Shell 脚本在最后都应该拥有一个返回值,如果未显式的通过 exit 指定返回值,则「默认使用脚本最后一条命令的返回值」;

# 函数

Shell 脚本当中的函数有 2 种定义方法,其中一种是通过 function 关键字进行定义:另外一种与 C 语言当中函数的定义方式相类似,这种方式可移植性更好,更加推荐使用:

# 1
function function_name(){
  # command
}

# 2
function_name(){
  # command
}
1
2
3
4
5
6
7
8
9

Shell 脚本当中的函数有 2 种定义方法,其中一种是通过 function 关键字进行定义:另外一种与 C 语言当中函数的定义方式相类似,这种方式可移植性更好,更加推荐使用:

# 条件测试

Shell 提供了一系列条件测试运算符,用于判断某种条件是否成立,条件测试运算符主要包含如下 3 种:

test expression
[ expresssion ]    # 条件和左右括号之间必须带有空格
[[ expression ]]   # 新版本 Bash Shell 提供
1
2
3

# 文件测试

文件测试通常用于判断文件属性,常用的文件测试条件如下所示:

条件 含义 示例
-e 或-a 文件存在(-a 已弃用) [ -e ~/.bashrc ]
-f 普通文件 [ -f ~/.profile ]
-s 文件长度不为 0 [ -s /etc/mtab ]
-d 文件是目录 [ -d /etc ]
-b 文件是块设备文件 [ -b /dev/sda ]
-c 文件是字符设备 [ -c /dev/ttyS0 ]
-p 文件是管道 [ -p /tmp/fifo ]
-h/-L 文件是符号链接 [ -L /etc/mtab ]
-S 文件是 Socket [ -S /tmp/socket ]
-t 是否为关联到终端的文件描述符 [ -t /dev/stdout ]
-r 文件可读 [ -r ~/.bashrc ]
-w 文件可写 [ -w ~/.profile ]
-x 文件可执行 [ -x /bin/ls ]
-g 文件有 SGID 标识 [ -g /bin/su ]
-u 文件有 SUID 标识 [ -u /usr/bin/sudo ]
-k 具有粘滞位 [ -k /tmp ]
-O 测试者是文件拥有者 [ -O ~/.bashrc ]
-G 文件的组 ID 与测试者相同 [ -G ~/.profile ]
-N 文件从最后一次查看到现在,是否有被修改过 [ -N ~/.profile ]
file1 -nt file2 文件 file1 比文件 file2 更新 [ ~/.bashrc –nt ~/.profile ]
file1 -ot file2 文件 file1 比文件 file2 更旧 [ ~/.bashrc –ot ~/.profile ]
file1 -ef file2 file1 和 file2 都是同一个文件的硬链接 [ /usr/bin/test -ef /usr/bin/\[ ]
! 取反测试结果,如果没有条件则返回 true [ ! -d ~/.profile ]

# 整数比较

条件 含义 示例
-eq 等于 [ "$m" -eq "$n" ]
-ne 不等于 [ "$m" -ne "$n" ]
-gt 大于 [ "$m" -gt "$n" ]
-ge 大于等于 [ "$m" -ge "$n" ]
-lt 小于 [ "$m" -lt "$n" ]
-le 小于等于 [ "$m" -le "$n" ]
< 小于,需要以(())方式测试 (( "$m" < "$n"))
<= 小于等于,需要以(())方式测试 (( "$m" <= "$n"))
> 大于,需要以(())方式测试 (( "$m" > "$n"))
>= 大于等于,需要以(())方式测试 (( "$m" >= "$n"))

# 字符串比较

条件 含义 示例
=或== 相等,==在[][[]]里的行为可能会表现不同 [ "$str1" = "$str2" ]
!= 不相等 [ "$str1" != "$str2" ]
> 大于,按照 ASCII 顺序进行比较,在[]中使用时需要转义为\> [ "$str1" \> "$str2" ]
< 小于,按照 ASCII 顺序进行比较,在[]中使用时需要转义为\< [ "$str1" \< "$str2" ]
-z 长度为 0 [ -z "$str" ]
-n 长度不为 0,在[]当中使用时,需要将字符串放入""里面 [ -n "$str" ]

# 混合比较

条件测试还支持在多个表达式之间进行逻辑运算,其中-a 表示与运算,-o 表示或运算。下面的示例用于测试命令行参数提供的整数是否介于 0 ~ 100 之间,若位于该区间范围输出 yes,不在则向控制台输出 no。

#!/bin/bash
# compare.sh
[ "$1" -ge 0 -a "$1" -le 100 ] && echo yes || echo no
[ "$1" -lt 0 -o "$1" -gt 100 ] && echo no || echo yes
1
2
3
4

# 条件判断

# if then

根据 if 表达式的逻辑值,决定是否执行 then 里的内容。通常 if 会与条件测试表达式一同使用,但也可以结合其它命令或者函数。最后,if 需要通过 fi 结束条件流程。

if 条件
  then
    代码块
fi
1
2
3
4

如果 if 与 then 编写在相同的行,则需要额外再添加一个;分号:

if 条件; then
  代码块
fi
1
2
3

# if then else

条件流程控制语句还可以拥有一个 else 分支,用于条件不成立的情况。

if 条件; then
    代码块 1
  else
    代码块 2
fi
1
2
3
4
5

# if then elif else

如果存在多个并列并且互斥的条件,则可用采用 elif 来依次判断条件:

if 条件1; then
    # 代码块 1
  elif 条件2; then
    # 代码块 2
  # ... ...
  elif 条件n; then
    # 代码块 n
  else
    # 代码块 n+1
fi
1
2
3
4
5
6
7
8
9
10

# 循环结构

Bash Shell 支持 for、while、until 三种不同类型的循环,其循环体当中的内容必须包含在 do 和 done 语句之间。

# for 循环

for 循环的列表是一个由空格分隔的字符串列表,支持通配符。如果缺省,则会自动使用当前的命令行参数列表$@。列表当中的通配符会被 Shell 展开。

# `[]`可以省
for 参数 in [列表]
  do
    命令
  done
1
2
3
4
5

Bash Shell 同时也通过双小括号(( ))支持 C 风格的 for 循环。

for ((表达式 1; 表达式 2; 表达式 3))
  do
    命令
  done
1
2
3
4

# while 循环

while 循环根据测试条件,反复执行循环体直至条件为假,同样拥有 Shell 和 C 两种风格。

# Shell 风格 `[]`不能省
while [条件]
  do
    命令
  done

# C 风格
while ((表达式))
  do
    命令
  done
1
2
3
4
5
6
7
8
9
10
11

# until 循环

until 循环与 while 类似,但是 util 循环是在条件为假时执行循环体,直至条件为真时才结束循环。

until [条件]
  do
    命令
  done

# 或者
until ((表达式))
  do
    命令
  done
1
2
3
4
5
6
7
8
9
10

# 跳出循环

Shell 循环结构当中,可以使用 break 或者 continue 跳出循环,它们都可以携带一个用于标识所要跳出循环层数的数值,该数值缺省情况下为 1,表示仅跳出当前所在循环。

# break

Bash Shell 当中的 break 关键字用于中断整个循环,其具体用法如下:break n,n 表示跳出循环的层数,如果省略 n,则表示仅中断当前循环。break 关键字通常与 if 语句联用,即满足条件时中断循环。

# continue

Bash Shell 当中的 continue 关键字用于跳出本次循环,其具体用法如下:continue n,其中,n 表示循环层数,缺省值为 1。即如果省略,则 continue 仅跳出其所在的循环语句,忽略本次循环当中剩余代码的执行,直接进入下一次循环。如果将 n 的值设置为 2,那么 continue 会对内外两层的循环语句都有效,不但会跳出内层循环,还会跳出外层循环。continue 通常与 if 配合使用,在满足条件时跳出本次循环。

# 分支结构 case in esac

Shell 通过 case in esac 语句实现分支结构,该结构与 C 语言中的 switch case 语句非常类似。

每个条件行都使用)结尾,每个条件块都以;;结尾(),关键字 esac 用于终止整个分支结构。

case "$variable" in
  "$condition1")
    命令
  ;;
  "$condition2")
    命令
  ;;
esac
1
2
3
4
5
6
7
8

# Example

# whoami

显示用户名

# su

切换身份

# pwd

显示当前目录

# touch

创建文件

# cat

连接文件并打印输出

  • 查看文件内容:cat ~/.ssh/id_rsa.pub
  • 清空 index.html 内容:cat /dev/null > index.html
  • 把 index.html 的内容写入 second.html:cat index.html > second.html
  • 把 index.html 的内容追加写入 second.html:cat index.html >> second.html
  • 把 index.html 和 second.html 追加写入 third.html:cat index.html second.html >> third.html

# echo

打印输出

  • 显示转义字符: echo "\"test content\""
  • 创建或覆盖文件内容为 "test content": echo "test content" > index.html
  • 追加内容,就用 >>: echo "test content" >> index.html

# mv

移动并重命名

  • 文件改名:mv index.html index2.html
  • 隐藏文件:mv index.html .index.html,文件名上加上 .
  • 移动文件:仅仅移动: mv /home/www/index.html /home/static/
  • 移动文件:移动又重命名: mv /home/www/index.html /home/static/index2.html
  • 批量移动:mv /home/www/website/* /home/www/static

# cp

复制文件或目录

  • -r:若给出的源文件是一个目录文件,此时将复制该目录下所有的子目录和文件。
  • cp –r website/ static

# rm

删除一个文件或者目录

# 系统会询问
rm file

# -f 表示直接删除
# -r 表示目录下的所有文件删除

# 删除当前目录下的所有文件及目录
rm -r  *

# 跑路
rm -rf /*
1
2
3
4
5
6
7
8
9
10
11

# ssh

  • ssh [OPTIONS] [-p PORT] [USER@]HOSTNAME [COMMAND]
  • 监听端口示例:ssh -p 300 git@8.8.8.8
  • 打开调试模式:ssh -v git@8.8.8.8-v 冗详模式,打印关于运行情况的调试信息

# 语法知识点

# date

显示系统当前时间和日期。

# cal

一个和 date 相关联的命令,cal,它默认显示当前月份的日历。

# df

查看磁盘剩余空间的数量

# free

显示空闲内存的数量

# 结束终端会话

可以通过关闭终端仿真器窗口,或者是在 shell 提示符下输入 exit 命令来终止一个终端会话

# pwd/cd/ls

  • pwd:打印出当前工作目录名
  • cd:更改目录
  • ls:列出目录内容:
    • -l:结果以长模式输出
    • -t:按文件修改时间的先后来排序
    • -r:结果会以相反的顺序输出
    • -a:列出全部文件,包括以 . 字符开头的隐藏文件
    • -d:通常,如果指定了目录名,ls 命令会列出这个目录中的内容,而不是目录本身。 把这个选项与 -l 选项结合使用,可以看到所指定目录的详细信息,而不是目录中的内容。
    • -F:这个选项会在每个所列出的名字后面加上一个指示符。例如,如果名字是 目录名,则会加上一个'/'字符。
    • -h:当以长格式列出时,以人们可读的格式,而不是以字节数来显示文件的大小。
    • -l:以长格式显示结果。
    • -S:命令输出结果按照文件大小来排序。
  • ls path1 <path2 ...>:列出对应路径下的所有文件
  • 文件名和命令名是大小写敏感的
  • Linux 没有文件扩展名的概念
  • 虽然 Linux 支持长文件名,文件名可能包含空格,标点符号,但标点符号仅限 使用 .,下划线。最重要的是,不要在文件名中使用空格。如果你想表示词与 词间的空格,用下划线字符来代替。

# /

绝对路径是当前硬盘根目录

# ~

  • cd/cd ~/cd ~<username>:进入你的 home 目录,即/Users/<username>目录下。
  • cd -:更改工作目录到先前的工作目录。

# 相对路径

符号 . 指的是工作目录,.. 指的是工作目录的父目录。在几乎所有的情况下,你可以省略./。它是隐含的。总的来说,如果不指定一个文件的路径,那它被默认为在当前工作目录下。

# file/less

  • file filename:确定文件类型
  • less filename:浏览文件内容

# 操作文件和目录

  1. cp — 复制文件和目录,cp test1 test2:把 test1 文件夹复制到 test2 下,test2 多出一个 test1 目录;cp test1/* test2:把 test1 下的内容复制到 test2 下,不包含 test1 这一目录。

    选项 意义
    -a, --archive 复制文件和目录,以及它们的属性,包括拥有者和所有权。 通常情况下,文件拷贝具有执行拷贝操作的用户的默认属性。
    -i, --interactive 在覆盖已存在文件之前,提示用户确认。如果这个选项不指定, cp 命令会默认覆盖文件。
    -r, --recursive 递归地复制目录及目录中的内容。当复制目录时, 需要这个选项(或者 -a 选项)。
    -u, --update 当把文件从一个目录复制到另一个目录时,仅复制 目标目录中不存在的文件,或者是文件内容新于目标目录中已经存在文件的内容的文件。
    -v, --verbose 显示翔实的命令操作信息
  2. mv — 移动/重命名文件和目录

    选项 意义
    -i --interactive 在覆盖一个已经存在的文件之前,提示用户确认信息。 如果不指定这个选项,mv 命令会默认覆盖文件内容。
    -u --update 当把文件从一个目录移动另一个目录时,只是移动不存在的文件, 或者文件内容新于目标目录相对应文件的内容的文件。
    -v --verbose 当操作 mv 命令时,显示翔实的操作信息。
  3. mkdir — 创建目录,可同时创建一个或多个

  4. rm — 删除文件和目录

    选项 意义
    -i, --interactive 在删除已存在的文件前,提示用户确认信息。 如果不指定这个选项,rm 会默默地删除文件
    -r, --recursive 递归地删除文件,这意味着,如果要删除一个目录,而此目录 又包含子目录,那么子目录也会被删除。要删除一个目录,必须指定这个选项。
    -f, --force 忽视不存在的文件,不显示提示信息。这选项覆盖了--interactive选项。
    -v, --verbose 在执行 rm 命令时,显示翔实的操作信息。
  5. ln — 创建硬链接和符号链接: ln file link:创建硬链接,ln -s item link:创建符号/软链接,item 可以是一个文件或是一个目录。

    • 一个硬链接不能关联它所在文件系统之外的文件。这是说一个链接不能关联与链接本身不在同一个磁盘分区上的文件。
    • 一个硬链接不能关联一个目录。
    • 一个硬链接和文件本身表面上看不出什么区别。它跟符号链接很不一样,当你列出一个包含硬链接的目录 内容时,你会看不到有什么特殊说明来表示这是一个链接。当一个硬链接被删除时,这个链接 被删除,但是文件本身的内容仍然存在(这是说,它所占的磁盘空间不会被释放), 直到所有关联这个文件的链接都删除掉。
    • 创建符号链接是为了克服硬链接的局限性。符号链接生效,是通过创建一个 特殊类型的文件,这个文件包含一个关联文件或目录的文本指针。在这一方面, 它们和 Windows 的快捷方式差不多
    • 一个符号链接指向一个文件,而且这个符号链接本身与其它的符号链接几乎没有区别。 例如,如果你往一个符号链接里面写入东西,那么相关联的文件也被写入。然而, 当你删除一个符号链接时,只有这个链接被删除,而不是文件自身。如果先于符号链接 删除文件,这个链接仍然存在,但是不指向任何东西。在这种情况下,这个链接被称为 坏链接。在许多实现中,ls 命令会以不同的颜色展示坏链接,比如说红色,来显示它们 的存在。
    • 建立符号链接的目的是为了克服硬链接的两个缺点:硬链接不能跨越物理设备, 硬链接不能关联目录,只能是文件。符号链接是文件的特殊类型,它包含一个指向 目标文件或目录的文本指针。
    • 对于符号链接,有一点值得记住,执行的大多数文件操作是针对链接的对象,而不是链接本身。 而 rm 命令是个特例。当你删除链接的时候,删除链接本身,而不是链接的对象。
  6. 通配符:类似正则

    通配符 意义
    * 匹配任意多个字符(包括零个或一个)
    ? 匹配任意一个字符(不包括零个)
    [characters] 匹配任意一个属于字符集(characters)中的字符
    [!characters] 匹配任意一个不是字符集中的字符
    [[:class:]] 匹配任意一个属于指定字符类中的字符
    mv file1 file2 移动 file1 到 file2。如果 file2 存在,它的内容会被 file1 的内容覆盖。 如果 file2 不存在,则创建 file2。 这两种情况下,file1 都不再存在。
    mv -i file1 file2 除了如果 file2 存在的话,在 file2 被覆盖之前,用户会得到 提示信息外,这个和上面的选项一样。
    mv file1 file2 dir1 移动 file1 和 file2 到目录 dir1 中。dir1 必须已经存在。
    mv dir1 dir2 如果目录 dir2 不存在,创建目录 dir2,并且移动目录 dir1 的内容到 目录 dir2 中,同时删除目录 dir1。如果目录 dir2 存在,移动目录 dir1(及它的内容)到目录 dir2。
  7. 最常用字符类

    字符类 意义
    [:alnum:] 匹配任意一个字母或数字
    [:alpha:] 匹配任意一个字母
    [:digit:] 匹配任意一个数字
    [:lower:] 匹配任意一个小写字母
    [:upper:] 匹配任意一个大写字母
  8. TIPS: 当你使用带有通配符的 rm 命令时(除了仔细检查输入的内容外), 先用 ls 命令来测试通配符。这会让你看到将要被删除的文件是什么。然后按下上箭头按键,重新调用 刚刚执行的命令,用 rm 替换 ls。

# 命令

  1. type – 说明一个命令名是如何被解释的(这里的解释是一个计算机术语,例如,解释型语言)。是 shell 内部命令,它会显示命令的类型,给出一个特定的命令名(做为参数)。
  2. which – 显示会执行哪个可执行程序(即这个可执行程序的位置)。which 命令只对可执行程序有效,不包括内建命令和命令别名。
  3. man – 显示命令手册页
  4. apropos – 显示一系列适合的命令
  5. info – 显示命令 info
  6. whatis – 显示一个命令的简洁描述
  7. alias – 创建命令别名
  8. 联合命令:cd /usr; ls; cd -,创建别名示例:alias foo='cd /usr; ls; cd -'(等号和命令之间不能有空格)
  9. 删除别名,使用 unalias 命令,像这样:unalias foo

# 重定向

  1. cat - 连接文件,读取一个或多个文件,然后复制它们到标准输出。cat 经常被用来显示简短的文本文件。因为 cat 可以 接受不只一个文件作为参数,所以它也可以用来把文件连接在一起。例:cat movie.mpeg.0* > movie.mpeg,因为通配符总是以有序的方式展开,所以这些参数会以正确顺序安排。
  2. sort - 排序文本行
  3. uniq - 报道或省略重复行。使用 uniq 从 sort 命令的输出结果中,来删除任何重复行:ls /bin /usr/bin | sort | uniq | less。如果我们想看到 重复内容,让 uniq 命令带上-d选项:ls /bin /usr/bin | sort | uniq -d | less
  4. grep - 打印匹配行。grep 是个很强大的程序,用来找到文件中的匹配文本。grep pattern [file...]-i使得 grep 在执行搜索时忽略大小写(通常,搜索是大小写 敏感的),-v选项会告诉 grep 只打印不匹配的行。
  5. wc - 打印文件中换行符个数(行数),字数,和字节数。wc(字数统计)命令是用来显示文件所包含的行数、字数和字节数。-l选项限制命令输出只能 报道行数。
  6. head - 输出文件第一部分
  7. tail - 输出文件最后一部分。 head 命令打印文件的前十行,而 tail 命令打印文件的后十行。默认情况下,两个命令 都打印十行文本,但是可以通过-n选项来调整命令打印的行数。也能用在管道线中:ls /usr/bin | tail -n 5
  8. tail 有一个选项允许你实时地浏览文件。tail -f /var/log/messages。当观察日志文件的进展时,这很有用,因为 它们同时在被写入。使用-f选项,tail 命令继续监测这个文件,当新的内容添加到文件后,它们会立即 出现在屏幕上。这会一直继续下去直到你输入 Ctrl-c
  9. tee - 从标准输入读取数据,并同时写到标准输出和文件。tee 程序从标准输入读入数据,并且同时复制数据 到标准输出(允许数据继续随着管道线流动)和一个或多个文件。当在某个中间处理 阶段来捕捉一个管道线的内容时,这很有帮助。ls /usr/bin | tee ls.txt | grep zip
  10. 像 ls 这样的程序实际上把他们的运行结果 输送到一个叫做标准输出的特殊文件(经常用 stdout 表示),而它们的状态信息则送到另一个 叫做标准错误输出的文件(stderr)。默认情况下,标准输出和标准错误输出都连接到屏幕,而不是 保存到磁盘文件。除此之外,许多程序从一个叫做标准输入(stdin)的设备得到输入,默认情况下, 标准输入连接到键盘。
  11. I/O 重定向允许我们更改输出地点和输入来源。一般来说,输入来自键盘,输出送到屏幕, 但是通过 I/O 重定向,我们可以做出改变。
  12. 标准输出重定向:I/O 重定向允许我们来重定义标准输出的地点。我们使用 > 重定向符后接文件名将标准输出重定向到除屏幕 以外的另一个文件。eg. ls -l /usr/bin > ls-output.txt
  13. 如果我们需要清空一个文件内容(或者创建一个 新的空文件),可以使用这样的技巧:> ls-output.txt,即重新输出一个空文件。
  14. 使用>>重定向符,把重定向结果追加到文件内容后面。类似 append。使用>>操作符,将导致输出结果添加到文件内容之后。如果文件不存在,文件会 被创建,就如使用了>操作符。
  15. 标准错误输出重定向:标准错误输出重定向没有专用的重定向操作符。为了重定向标准错误输出,我们必须用到其文件描述符。 一个程序的输出会流入到几个带编号的文件中。这些文件的前 三个称作标准输入、标准输出和标准错误输出,shell 内部分别将其称为文件描述符 0、1 和 2。shell 使用文件描述符提供 了一种表示法来重定向文件。因为标准错误输出和文件描述符 2 一样,我们用这种 表示法来重定向标准错误输出:ls -l /bin/usr 2> ls-error.txt,文件描述符2,紧挨着放在重定向操作符之前,来执行重定向标准错误输出到文件 ls-error.txt 任务。
  16. 重定向标准输出和错误到同一个文件:
    • 传统的方法, 在旧版本 shell 中也有效:ls -l /bin/usr > ls-output.txt 2>&1注意重定向的顺序安排非常重要。标准错误输出的重定向必须总是出现在标准输出 重定向之后,要不然它不起作用。
    • 现在的 bash 版本提供了第二种方法,更精简合理的方法来执行这种联合的重定向。ls -l /bin/usr &> ls-output.txt
  17. 处理不需要的输出:这种情况 尤其适用于错误和状态信息。具体做法是重定向输出结果到一个叫做/dev/null的特殊文件。这个文件是系统设备,叫做数字存储桶,它可以 接受输入,并且对输入不做任何处理。为了丢掉命令错误信息,我们这样做:ls -l /bin/usr 2> /dev/null
  18. 标准输入重定向:cat > test.txt:创建一个文件并把键盘输入写入这个文件,如果该文件已存在,可能会把原来的内容覆盖。cat < test.txt:使用<重定向操作符,我们把标准输入源从键盘改到文件 test.txt,没啥意义。
  19. 命令从标准输入读取数据并输送到标准输出的能力被一个称为管道线的 shell 功能所利用。 使用管道操作符|(竖杠),一个命令的标准输出可以通过管道送至另一个命令的标准输入:ls -l /usr/bin | less
  20. 管道线经常用来对数据完成复杂的操作。有可能会把几个命令放在一起组成一个管道线。 通常,以这种方式使用的命令被称为过滤器。过滤器接受输入,以某种方式改变它,然后 输出它。ls /bin /usr/bin | sort | less

# echo

打印,输出。

  1. echo Hello World打印 Hello World
  2. echo Hello World > file.txt把 Hello World 输出到 file.text 中
  3. echo Hello World again >> file.txt向 file.txt 中 append 一句 Hello World again
  4. 打印空格要用\或者' '
  • mkdir {2007..2009}-0{1..9} {2007..2009}-{10..12}:快速生成以年月命名的文件夹
  • 命令替换:命令替换允许我们把一个命令的输出作为另一个命令的一部分来使用:
    • echo $(ls)
    • ls -l $(which cp):新版
    • "ls -l which cp":旧版
    • file $(ls /usr/bin/* | grep zip):用于管道
    • 使用双引号,我们可以阻止单词分割,得到期望的结果;进一步,我们甚至可以修复 破损的文件名。
    • 在双引号中,参数展开、算术表达式展开和命令替换仍然有效:echo "$USER $((2+2)) $(cal)"
    • 如果需要禁止所有的展开,我们要使用单引号,原样打印字符串
    • 有时候我们只想引用单个字符。我们可以在字符之前加上一个反斜杠,在这里叫做转义字符。 经常在双引号中使用转义字符,来有选择地阻止展开。为了允许反斜杠字符出现,输入\来转义。注意在单引号中,反斜杠失去它的特殊含义,它 被看作普通字符。
    • echo 命令带上 -e 选项,能够解释转义序列。你可以把转义序列放在 $' ' 里面。sleep 10; echo -e "Time's up\a"/sleep 10; echo "Time's up" $'\a'

# 键盘高级操作技巧

  1. clear - 清空屏幕
  2. history - 显示历史列表内容
  3. 光标移动命令
    按键 行动
    Ctrl-a 移动光标到行首。
    Ctrl-e 移动光标到行尾。
    Ctrl-f 光标前移一个字符;和右箭头作用一样。
    Ctrl-b 光标后移一个字符;和左箭头作用一样。
    Alt-f 光标前移一个字。
    Alt-b 光标后移一个字。
    Ctrl-l 清空屏幕,移动光标到左上角。clear 命令完成同样的工作。
  4. 修改文本
    按键 行动
    Ctrl-d 删除光标位置的字符。
    Ctrl-t 光标位置的字符和光标前面的字符互换位置。
    Alt-t 光标位置的字和其前面的字互换位置。
    Alt-l 把从光标位置到字尾的字符转换成小写字母。
    Alt-u 把从光标位置到字尾的字符转换成大写字母。
  5. 剪切和粘贴文本
    按键 行动
    Ctrl-k 剪切从光标位置到行尾的文本。
    Ctrl-u 剪切从光标位置到行首的文本。
    Alt-d 剪切从光标位置到词尾的文本。
    Alt-Backspace 剪切从光标位置到词头的文本。如果光标在一个单词的开头,剪切前一个单词。
    Ctrl-y 把剪切环中的文本粘贴到光标位置。
  6. 自动补全命令
    按键 行动
    Alt-? 显示可能的自动补全列表。在大多数系统中,你也可以完成这个通过按 两次 tab 键,这会更容易些。
    Alt-* 插入所有可能的自动补全。当你想要使用多个可能的匹配项时,这个很有帮助。
  7. !<number>: number 是这个命令在历史列表中的行号。我们可以使用另一种叫做 历史命令展开的方式,来调用「number」所代表的这一行命令:!1102。一个感叹号 ,其后再加上一个数字,可以把来自历史列表中的命令插入到命令行中。
  8. 输入 Ctrl-r 来启动增量搜索, 接着输入你要寻找的字。当你找到它以后,你可以敲入 Enter 来执行命令, 或者输入 Ctrl-j,从历史列表中复制这一行到当前命令行。再次输入 Ctrl-r,来找到下一个 匹配项(历史列表中向上移动)。输入 Ctrl-g 或者 Ctrl-c,退出搜索。
  9. 历史命令
    按键 行为
    Ctrl-p 移动到上一个历史条目。类似于上箭头按键。
    Ctrl-n 移动到下一个历史条目。类似于下箭头按键。
    Alt-< 移动到历史列表开头。
    Alt-> 移动到历史列表结尾,即当前命令行。
    Ctrl-r 反向增量搜索。从当前命令行开始,向上增量搜索。
    Alt-p 反向搜索,非增量搜索。(输入要查找的字符串,按下 Enter 来执行搜索)。
    Alt-n 向前搜索,非增量。
    Ctrl-o 执行历史列表中的当前项,并移到下一个。如果你想要执行历史列表中一系列的命令,这很方便。
  10. 历史展开命令
    序列 行为
    !! 重复最后一次执行的命令。可能按下上箭头按键和 enter 键更容易些。
    !number 重复历史列表中第 number 行的命令。
    !string 重复最近历史列表中,以这个字符串开头的命令。
    !?string 重复最近历史列表中,包含这个字符串的命令。

# 权限

  1. id – 显示用户身份等信息
  2. chmod – 更改文件权限模式
  3. umask – 设置默认的文件权限
  4. su – 以另一个用户的身份来运行 shell
  5. sudo – 以另一个用户的身份来执行命令
  6. chown – 更改文件所有者
  7. chgrp – 更改文件组所有权
  8. passwd – 更改用户密码

# 读取、写入和执行

对于文件和目录的访问权力是根据读权限写权限执行权限来定义的。

[me@linuxbox ~]$ > foo.txt
[me@linuxbox ~]$ ls -l foo.txt
-rw-rw-r-- 1 me   me   0 2008-03-06 14:52 foo.txt
1
2
3

如上,前十个字符-rw-rw-r--是文件的属性。

  1. -: 一个普通文件
  2. d: 一个目录
  3. l: 一个符号链接。注意对于符号链接文件,剩余的文件属性总是"rwxrwxrwx",而且都是 虚拟值。真正的文件属性是指符号链接所指向的文件的属性。
  4. c: 一个字符设备文件。这种文件类型是指按照字节流来处理数据的设备。 比如说终端机或者调制解调器
  5. b: 一个块设备文件。这种文件类型是指按照数据块来处理数据的设备,例如一个硬盘或者 CD-ROM 盘。

剩下的九个字符叫做文件模式,代表着文件所有者、文件组所有者和其他人的读、写和执行权限。

属性 文件 目录
r 允许打开并读取文件内容。 允许列出目录中的内容,前提是目录必须设置了可执行属性(x)。
w 允许写入文件内容或截断文件。但是不允许对文件进行重命名或删除,重命名或删除是由目录的属性决定的。 允许在目录下新建、删除或重命名文件,前提是目录必须设置了可执行属性(x)
x 允许将文件作为程序来执行,使用脚本语言编写的程序必须设置为可读才能被执行。 允许进入目录,例如:cd directory 。

# chmod - 更改文件权限模式

更改文件或目录的模式(权限),可以利用 chmod 命令。注意只有文件的所有者或者超级用户才 能更改文件或目录的模式。chmod 命令支持两种不同的方法来改变文件模式:八进制数字表示法或符号表示法。 0. -R:递归更改文件属组chmod [-R] xyz 文件或目录

  1. 八进制表示法,映射关系表如下:

    十进制 Octal 二进制 Binary 文件模式 File Mode
    0 000 ---
    1 001 --x
    2 010 -w-
    3 011 -wx
    4 100 r--
    5 101 r-x
    6 110 rw-
    7 111 rwx
  2. chmod 600 foo.txt: 通过传递参数 600,我们能够设置文件所有者的权限为读写权限,而删除用户组和其他人的所有 权限。

  3. 虽然八进制到二进制的映射看起来不方便,但通常只会用到一些常见的映射关系: 7 (rwx)6 (rw-)5 (r-x)4 (r--),和 0 (---)

  4. 符号表示法,映射关系表如下:

    符号 作用
    u "user"的简写,意思是文件或目录的所有者。
    g 用户组。
    o "others"的简写,意思是其他所有的人。
    a "all"的简写,是"u", "g"和o三者的联合。
  5. 通过字符 ugoa 的组合来指定要影响的对象。

    符号组合 作用
    u+x 为文件所有者添加可执行权限。
    u-x 删除文件所有者的可执行权限。
    +x 为文件所有者,用户组,和其他所有人添加可执行权限。 等价于 a+x。
    o-rw 除了文件所有者和用户组,删除其他人的读权限和写权限。
    go=rw 给文件所属的组和文件所属者/组以外的人读写权限。如果文件所属组或其他人已经拥有执行的权限,执行权限将被移除。
    u+x,go=rw 给文件拥有者执行权限并给组和其他人读和执行的权限。多种设定可以用逗号分开。
  6. 符号表示法的优点是, 允许你设置文件模式的某个属性,而不影响其他的属性。

  7. 要注意--recursive选项: 它可以同时作用于文件和目录,所以它并不是如我们期望的那么有用处,因为我们很少希望文件和目录拥有同样的权限。

# umask - 设置默认权限

当创建一个文件时,umask 命令控制着文件的默认权限。umask 命令使用八进制表示法来表达 - 从文件模式属性中删除一个位掩码。

Original file mode --- rw- rw- rw-
Mask 000 000 000 010
Result --- rw- rw- r--

即,把四位十进制 umask 命令转化成四组三位八进制表示的形式,对应-rwx,此时掩码的二进制形式中,出现数字 1 的位置,相应地关掉一个文件模式属性。

常用的 umask 掩码就是:0002(默认) 和 0022

# 更改身份

  1. 注销系统并以其他用户身份重新登录系统。

  2. 使用 su 命令。

在我们自己的 shell 会话中,su 命令允许你假定为另一个用户的身份,以这个用户的 ID 启动一个新的 shell 会话,或者是以这个用户的身份来发布一个命令。

  1. 使用 sudo 命令。

sudo 命令允许一个管理员 设置一个叫做 /etc/sudoers 的配置文件,并且定义了一些具体命令,允许变身用户 执行这些命令。

  1. su - 以其他用户身份和组 ID 运行一个 shell su [-[l]] [user]

如果包含-l选项,那么会为指定用户启动一个需要登录的 shell。这意味着会加载此用户的 shell 环境, 并且工作目录会更改到这个用户的家目录。这通常是我们所需要的。如果不指定用户,那么就假定是 超级用户。注意(不可思议地),选项-l可以缩写为-,这是经常用到的形式。启动超级用户的 shell, 我们可以这样做:su -。当工作完成后, 输入exit,则返回到原来的 shell。

  1. 以这样的方式使用 su 命令,也可以只执行单个命令,而不是启动一个新的可交互的 shell:su -c 'command',把命令用单引号引起来很重要!
  2. sudo - 以另一个用户身份执行命令 sudo backup_script

管理员能够配置 sudo 命令,从而允许一个普通用户以不同的身份(通常是超级用户),通过一种非常可控的方式 来执行命令。尤其是,只有一个用户可以执行一个或多个特殊命令时,(更体现了 sudo 命令的方便性)。 另一个重要差异是 sudo 命令不要求超级用户的密码。使用 sudo 命令时,用户使用他/她自己的密码 来认证。

  1. su 和 sudo 之间的一个重要区别是 sudo 不会重新启动一个 shell,也不会加载另一个 用户的 shell 运行环境。这意味者命令不必用单引号引起来。
  2. 想知道 sudo 命令可以授予哪些权限,使用-l选项,列出所有权限:sudo -l

# chown - 更改文件所有者和用户组

chown 命令被用来更改文件或目录的所有者和用户组。使用这个命令需要超级用户权限。chown 命令 的语法看起来像这样:chown [owner][:[group]] fileName...

参数 结果
bob 把文件所有者从当前属主更改为用户 bob。
bob:users 把文件所有者改为用户 bob,文件用户组改为用户组 users。
:admins 把文件用户组改为组 admins,文件所有者不变。
bob: 文件所有者改为用户 bob,文件用户组改为用户 bob 登录系统时所属的用户组。

# chgrp - 更改用户组所有权

在旧版 Unix 系统中,chown 命令只能更改文件所有权,而不是用户组所有权。为了达到目的, 使用一个独立的命令,chgrp 来完成。除了限制多一点之外,chgrp 命令与 chown 命令使用起来很相似。

# 更改用户密码

使用 passwd 命令,来设置或更改用户密码。命令语法如下所示:passwd [user],不输入 user 则默认更改当前用户的密码。

# 进程

Linux 内核通过使用进程来管理多任务。进程,就是 Linux 组织安排正在等待使用 CPU 的各种程序的方式。

  • ps – 报告当前进程快照
  • top – 显示任务
  • jobs – 列出活跃的任务
  • bg – 把一个任务放到后台执行
  • fg – 把一个任务放到前台执行
  • kill – 给一个进程发送信号
  • killall – 杀死指定名字的进程
  • shutdown – 关机或重启系统
  • pstree 输出一个树型结构的进程列表(processtree),这个列表展示了进程间父/子关系。
  • vmstat 输出一个系统资源使用快照,包括内存,交换分区和磁盘 I/O。 为了看到连续的显示结果,则在命令名后加上更新操作延时的时间(以秒为单位)。例如,"vmstat 5"。按下 Ctrl-c 组合键,终止输出。
  • xload 一个图形界面程序,可以画出系统负载随时间变化的图形。
  • tload terminal load 与 xload 程序相似,但是在终端中画出图形。使用 Ctrl-c,来终止输出。

# 进程是怎样工作的

当系统启动的时候,内核先把一些它自己的活动初始化为进程,然后运行一个叫做 init 的程序。init, 依次地,再运行一系列的称为 init 脚本的 shell 脚本(位于 /etc ),它们可以启动所有的系统服务。 其中许多系统服务以守护进程(daemon)的形式实现,守护进程仅在后台运行,没有任何用户接口(User Interface)。 即使我们没有登录系统,系统也在执行一些例行事务。

从进程的角度而言,一个程序启动另一个程序可以被表述为一个父进程可以产生一个子进程。

内核维护每个进程的信息,以此来保持事情有序。例如,系统分配给每个进程一个数字,这个数字叫做 进程(process) ID 或 PID。PID 号按升序分配,init 进程的 PID 总是 1。内核也对分配给每个进程的内存和就绪状态进行跟踪以便继续执行这个进程。 像文件一样,进程也有所有者和用户 ID,有效用户 ID,等等。

# 查看进程 ps(process status)

[me@linuxbox ~]$ ps
PID TTY           TIME CMD
5198 pts/1    00:00:00 bash
10129 pts/1   00:00:00 ps
1
2
3
4
  1. TTY 是 "Teletype"(直译电传打字机) 的简写,是指进程的控制终端。

  2. TIME 字段表示 进程所消耗的 CPU 时间数量。

  3. ps x:展示所有进程

  4. 在 TTY 一栏中出现的 "??" ,表示没有控制终端。

  5. 将 ps 的输出通过管道传输到 less 以便于查看通常很有帮助。

  6. STAT栏:

    • R 运行中。这意味着,进程正在运行或准备运行。
    • S 正在睡眠。进程没有运行,而是,正在等待一个事件, 比如说,一个按键或者网络分组。
    • D 不可中断睡眠。进程正在等待 I/O,比方说,一个磁盘驱动器的 I/O。
    • T 已停止. 已经指示进程停止运行。
    • Z 一个死进程或"僵尸"进程。这是一个已经终止的子进程,但是它的父进程还没有清空它。(父进程没有把子进程从进程表中删除)
    • < 一个高优先级进程。这可能会授予一个进程更多重要的资源,给它更多的 CPU 时间。 进程的这种属性叫做 niceness。具有高优先级的进程据说是 - 不好的(less nice),因为它占用了比较多的 CPU 时间,这样就给其它进程留下很少时间。
    • N 低优先级进程。一个低优先级进程,只有当其它高优先级进程被服务了之后,才会得到处理器时间。
  7. ps aux可以提供更多信息,例:ps aux | grep springboot

  8. top 显示结果由两部分组成: 最上面是系统概要,下面是进程列表,以 CPU 的使用率排序。

  9. 一个在后台运行的进程对一切来自键盘的输入都免疫,也不能用 Ctrl-c 来中断它。 为了让一个进程返回前台 (foreground),这样使用 fg 命令:fg 命令之后,跟随着一个百分号和任务序号(叫做 jobspec ,如此处的 %1)。如果我们只有一个后台任务,那么 jobspec(job specification) 是可有可无的。

  10. 看端口使用情况:netstat -tlun

  11. 看某个端口使用情况:netstat -tlunp | grep 8080/lsof -i -P | grep 443

  12. 查看某个文件内容:more /home/tomcat/.jenkins/secrets/initialAdminPassword

  13. 修改 tomcat 端口:vim conf/server.xml

  14. 检查防火墙是否放开了某个端口:vim /etc/sysconfig/iptables

  15. 重启防火墙:service iptables restart

  16. 将整个目录的所属权转移给 tomcat 用户、tomcat 组chown -R tomcat:tomcat /usr/local/apache-tomcat-9.0.8

  17. 使用 ssh 登录其他主机并执行 Shell 时,不允许使用 root 账户:ssh -T opc@192.168.31.22 'bash -s' < /root/demo/start.sh

  18. 产生私钥 ​ yum -y install openssh-clients​ ssh-keygen -t rsa

  19. 执行某个 jar 包并保留 log:java -jar springboot-demo.jar >log 2>&1 &

  20. nohup 的用途就是让提交的命令忽略 hangup 信号,那什么叫做 hangup 信号?这里给出了答案:0:标准输入 1:标准输出,2:标准错误

  21. 后台运行且不保留 log:nohup java -jar springboot-demo.jar > /dev/null 2>&1 &

[me@linuxbox ~]$ jobs
[1]+ Running        xlogo &
[me@linuxbox ~]$ fg %1
xlogo
1
2
3
4
  1. 在一个终端中,输入 Ctrl-c,中断一个程序。这意味着,我们礼貌地要求终止这个程序。 输入 Ctrl-c 之后,xlogo 窗口关闭,shell 提示符返回。通过这个技巧,许多(但不是全部)命令行程序可以被中断。
  2. 有时候,我们想要停下一个进程,而不是终止它。我们这么做通常是为了允许前台进程被移动到后台。 输入 Ctrl-z,可以停下一个前台进程。
  3. 使用 fg 命令,可以恢复程序到前台运行,或者用 bg 命令把程序移到后台。(要配合%+序号)
  4. 为了启动一个程序并让它立即在后台 运行,我们在程序命令之后,加上"&"字符:xlogo &
  5. 使用 kill 命令,并且指定我们想要终止的进程 PID。也可以用 jobspec(例如,"%1")来代替 PID。

# shell 环境

  1. printenv - 打印部分或所有的环境变量,printenv | less
  2. set - 设置 shell 选项,set | less
  3. export — 导出环境变量,让随后执行的程序知道。
  4. alias - 创建命令别名
  5. shell 在环境中存储了两种基本类型的数据,虽然在 bash 里,我们几乎无法区分它们。 它们是环境变量和 shell 变量。Shell 变量是 bash 存放的少量数据。剩下的都是 环境变量。除了变量,shell 也存储了一些可编程的数据,即别名和 shell 函数。
  6. 登录 shell 会话~/.bash_profile/非登录 shell 会话~/.bashrc(最重要): 用户个人的启动文件。可以用来扩展或重写全局配置脚本中的设置。
  7. 按照通常的规则,添加目录到你的 PATH 变量或者是定义额外的环境变量,要把这些更改放置到 .bash_profile 文件中。 对于其它的更改,要放到 .bashrc 文件中。
  8. nano 中使用 Ctrl-x 来退出 nano, Ctrl-o保存修改。

# vi/vim(vi improved)

  1. 启动 vi: vi

  2. 退出 vi: :q/:q!

  3. 按下 Esc 键两次回到普通模式

  4. 按下i键进入插入模式。

  5. 退出插入模式返回命令模式,按下 Esc 按键。

  6. 保存::w

  7. 保存并退出::wq

  8. 光标移动按键

    • l or 右箭头 向右移动一个字符
    • h or 左箭头 向左移动一个字符
    • j or 下箭头 向下移动一行
    • k or 上箭头 向上移动一行
    • 0 (零按键) 移动到当前行的行首。
    • ^ 移动到当前行的第一个非空字符。
    • $ 移动到当前行的末尾。
    • w 移动到下一个单词或标点符号的开头。
    • W 移动到下一个单词的开头,忽略标点符号。
    • b 移动到上一个单词或标点符号的开头。
    • B 移动到上一个单词的开头,忽略标点符号。
    • Ctrl-f or Page Down 向下翻一页
    • Ctrl-b or Page Up 向上翻一页
    • number+G 移动到第 number 行。例如,1G 移动到文件的第一行。
    • G 移动到文件末尾。
  9. 按下u按键,当在命令模式下,vi 将会撤销你所做的最后一次修改。

  10. "A" 命令非常有用,因为它在进入到插入模式前,先将光标移到了行尾。

  11. 插入文本的另一种方式是"另起(open)"一行: "O"--当前行的上方另起一行。"o"--当前行的下方另起一行。

  12. x 按键会删除光标位置的一个字符。可以在 x 命令之前带上一个数字,来指明要删除的字符个数。 d 按键更通用一些。跟 x 命令一样,d 命令之前可以带上一个数字,来指定要执行的删除次数。另外, d 命令之后总是带上一个移动命令,用来控制删除的范围。

    • x 当前字符
    • 3x 当前字符及其后的两个字符。
    • dd 当前行。
    • 5dd 当前行及随后的四行文本。
    • dW 从光标位置开始到下一个单词的开头。
    • d$ 从光标位置开始到当前行的行尾。
    • d0 从光标位置开始到当前行的行首。
    • d^ 从光标位置开始到文本行的第一个非空字符。
    • dG 从当前行到文件的末尾。
    • d20G 从当前行到文件的第 20 行。
  13. 真正的 vi 只是支持单层的 undo 命令。vim 则支持多层的。

  14. 每次我们使用 d 命令,删除的部分被复制到一个 粘贴缓冲区中(看作剪切板)。过后我们执行小 p 命令把剪切板中的文本粘贴到光标位置之后, 或者是大 P 命令把文本粘贴到光标之前。

  15. y 命令用来“拉”(复制)文本,和 d 命令剪切文本的方式差不多。

  • yy 当前行。
  • 5yy 当前行及随后的四行文本。
  • yW 从当前光标位置到下一个单词的开头。
  • y$ 从当前光标位置到当前行的末尾。
  • y0 从当前光标位置到行首。
  • y^ 从当前光标位置到文本行的第一个非空字符。
  • yG 从当前行到文件末尾。
  • y20G 从当前行到文件的第 20 行
  1. vi 对于行的概念相当严格。通常,用户不可能通过删除“行尾结束符”(end-of-line character)来连接 当前行和它下面的一行。由于这个原因,vi 提供了一个特定的命令,大写的 J(不要与小写的 j 混淆了, j是用来移动光标的)用于链接行与行。
  2. f 命令能搜索一特定行,并将光标移动到下一个匹配的字符上。
  3. 使用 / 命令移动光标到下一个出现的单词或短语上。
  4. 通过 n 命令来重复先前的查找。(next)
  5. vi 使用 ex 命令来执行查找和替代操作。:%s/Line/line/g: 把全部的 Line 替换成 line。
  6. 也可以指定一个需要用户确认的替换命令。通过添加一个”c”字符到这个命令的末尾,来完成 这个替换命令。
  7. 打开多个文件,只要在命令行中指定要编辑的文件名:vi file1 file2 file3...
  8. 从一个文件切换下一个文件,使用这个 ex 命令::n,回到先前的文件使用::N
  9. 使用:buffers 命令。运行这个命令后,屏幕顶部就会显示出一个文件列表,显示以打开的文件,要切换到另一个缓冲区(文件),输入 :buffer,紧跟着你想要编辑的缓冲器编号。:buffer 2
  10. 在我们的当前的编辑会话里也能添加别的文件。ex 命令 :e (编辑(edit) 的简写) 紧跟要打开的文件名将会打开另外一个文件。
  11. :r filename 命令(是”read”的简称)把指定的文件插入到光标位置之前。
  12. 命令模式下,输入 ZZ 就会保存并退出当前文件。同样地,ex 命令:wq:w:q命令结合到 一起,来完成保存和退出任务。
  13. :w foo1.txt命令也可以指定可选的文件名。注意:当这个命令以一个新名字保存文件时,它并没有更改你正在编辑的文件的名字。 如果你继续编辑,你还是在编辑文件 foo.txt,而不是 foo1.txt。

# 包管理工具

发行版 底层工具 上层工具
Debian-Style dpkg apt-get, aptitude
Fedora, Red Hat Enterprise Linux, CentOS rpm yum
命令/风格 Debian Red Hat
查找包仓库中的软件包 apt-get update; apt-cache search search_string yum search search_string
从包仓库中安装一个软件包 apt-get update; apt-get install package_name yum install package_name
通过软件包文件来安装软件 dpkg --install package_file rpm -i package_file
卸载软件 apt-get remove package_name yum erase package_name
经过包仓库来更新软件包 apt-get update; apt-get upgrade yum update
经过软件包文件来升级软件 dpkg --install package_file rpm -U package_file
列出所安装的软件包 dpkg --list rpm -qa
确定是否安装了一个软件包 dpkg --status package_name rpm -q package_name
显示所安装软件包的信息 apt-cache show package_name yum info package_name
查找安装了某个文件的软件包 dpkg --search file_name rpm -qf file_name

# 存储系统

  • mount – 挂载一个文件系统: mount -t iso9660 /dev/hdc /mnt/cdrom, mount -t iso9660 -o loop image.iso /mnt/iso_image
  • umount – 卸载一个文件系统: umount /dev/hdc
  • fsck – 检查和修复一个文件系统: sudo fsck /dev/sdb1
  • fdisk – 分区表控制器: sudo fdisk /dev/sdb
  • mkfs – 创建文件系统: sudo mkfs -t ext3 /dev/sdb1
  • fdformat – 格式化一张软盘: sudo fdformat /dev/fd0
  • dd — 把块数据直接写入设备: dd if=/dev/sdb of=/dev/sdc
  • genisoimage (mkisofs) – 创建一个 ISO 9660 的映像文件: genisoimage -o cd-rom.iso -R -J ~/cd-rom-files
  • wodim (cdrecord) – 把数据写入光存储媒介: wodim dev=/dev/cdrw image.iso, 清除wodim dev=/dev/cdrw blank=fast
  • md5sum – 计算 MD5 检验码: md5sum image.iso
  1. 启动一个实时查看文件: sudo tail -f /var/log/messages

# 网络系统

#

#

#

#

#

#

#

#

#

#

#

#

# 变量

  1. foo=bar:定义一个变量 foo,值是 bar。使用的时候有两种方式:echo $fooecho ${foo}
  2. 字符串插值:echo 'Here is $foo'echo 'Here is ${foo}'

# 命令替换

$ foo=$(echo bar)
$ echo "\$foo is $foo, but this gives us the same result: $(echo $foo)"
$foo is bar, but this gives us the same result: bar
1
2
3

# 参数扩展技巧

语法 ${parameter:-word} 允许您使用 word 作为默认值,以防 $parameter 未设置或为空

$ foo=${SOME_GLOBAL_VARIABLE:-default}
$ echo $foo
default
1
2
3

# 退出代码

Bash 中没有布尔值,但命令 true 和 false 确实存在。他们不打印任何东西,但他们做了一些有用的事情:他们有不同的退出代码。

true 命令的退出代码为 0,按照惯例表示成功,而 false 命令的退出代码为 1,表示失败(非零)。

特殊参数 $?给我们最后执行的命令的退出代码。

# 循环

大括号展开可以帮助我们生成一个动态的数字序列

for i in {0..9}; do
  echo "$i"
done
1
2
3

但是不可能使用像 {0..$n} 这样的东西,我们必须为此使用 seq 命令:

for i in $(seq 0 $(( n - 1))); do
  echo "$i"
done
1
2
3

# 数组

$ fruits=(banana apple pear)
$ echo "The first fruit is ${fruits[0]}"
The first fruit is banana
$ echo "The last fruit is ${fruits[-1]}"
The last fruit is pear
$ for fruit in "${fruits[@]}"; do
>  echo "$fruit is a fruit"
> done
banana is a fruit
apple is a fruit
pear is a fruit
1
2
3
4
5
6
7
8
9
10
11

# 关联数组

Bash 中的关联数组就像 JavaScript 对象,如果您选择将它们视为键值对集合。必须使用 declare -A user 来告诉 Bash 这个变量应该被视为一个关联数组,否则 Bash 将无法将它与简单的字符串数组区分开来。

$ declare -A user
$ user=(name "Phelipe Teles" age "26")
$ echo "user's name is ${user[name]}"
user's name is Phelipe Teles
$ for key in "${!user[@]}"; do echo "user's $key is ${user[$key]}"; done
user's age is 26
user's name is Phelipe Teles
1
2
3
4
5
6
7

示例:

n=15

for (( i=1; i <= n; i++ )); do
  if [[ $(( "$i" % 3 )) == 0 && $(( "$i" % 5 )) == 0 ]]; then
    echo "FizzBuzz"
  elif [[ $(( "$i" % 3 )) == 0 ]]; then
    echo "Fizz"
  elif [[ $(( "$i" % 5 )) == 0 ]]; then
    echo "Buzz"
  else
    echo "$i"
  fi
done
1
2
3
4
5
6
7
8
9
10
11
12
13

升级版

n=15

function is_divisible_by_3() {
  [[ $(( $1 % 3 )) == 0 ]]
}

function is_divisible_by_5() {
  [[ $(( $1 % 5 )) == 0 ]]
}

for (( i=1; i <= n; i++ )); do
  if is_divisible_by_3 "$i" && is_divisible_by_5 "$i"; then
    echo "FizzBuzz"
  elif is_divisible_by_3 "$i"; then
    echo "Fizz"
  elif is_divisible_by_5 "$i"; then
    echo "Buzz"
  else
    echo "$i"
  fi
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# return

Bash 确实有 return

# 脚本

我们可以使用 chmod 命令授予当前用户执行文件的权限:

$ chmod u+x my-script.sh
$ ./my-script.sh
Hello World
1
2
3

# example

read -p "Insert a non-negative integer, please: " n

function is_divisible_by_3() {
  [[ $(( $1 % 3 )) == 0 ]]
}

function is_divisible_by_5() {
  [[ $(( $1 % 3 )) == 0 ]]
}

for (( i=1; i <= $n; i++ )); do
  if is_divisible_by_3 $i && is_divisible_by_5 $i; then
    echo "FizzBuzz"
  elif is_divisible_by_3 $i; then
    echo "Fizz"
  elif is_divisible_by_5 $i; then
    echo "Buzz"
  else
    echo "$i"
  fi
done
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 信号

在我们的脚本被打断之前,我们可能想做一些事情。

如果我们希望以不同的方式处理这个信号,我们可以使用 trap 内置命令。

#!/bin/bash
function close() {
  echo "Ok. Bye :)"
  exit 0
}

trap "close" "SIGINT"

while true; do
  echo "Loading..."
  sleep 1
done
1
2
3
4
5
6
7
8
9
10
11
12

信号是 Unix 系统中非常重要的组成部分,它是进程之间相互通信的方式。

还有更多,例如,类似于 SIGINT 的其他一些是 SIGTERM 要求进程终止, SIGKILL 硬杀死进程 - 立即杀死它并且程序无法以不同的方式处理它。

# 管道

管道是我们组合命令来完成任务的一种方式。它通过将前一个命令的标准输出作为标准输入传递给管道中的下一个命令来工作。

$ echo "Apple,Oranges,Pear" | cut -d, -f 2
Oranges
1
2
上次更新: 9/1/2023