Linux系统知识
一、inode
1.1 什么是inode
理解inode,要从文件储存说起。
文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(Sector)。每个扇区储存512字节(相当于0.5KB)。
操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个"块"(block)。这种由多个扇区组成的"块",是文件存取的最小单位。"块"的大小,最常见的是4KB,即连续八个 sector组成一个 block。
文件数据都储存在"块"中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为"索引节点"。
每一个文件都有对应的inode,里面包含了与该文件有关的一些信息。
1.2 inode的内容
inode包含文件的元信息,具体来说有以下内容:
`* 文件的字节数 * 文件拥有者的User ID * 文件的Group ID * 文件的读、写、执行权限 * 文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。 * 链接数,即有多少文件名指向这个inode * 文件数据block的位置`
Linux下可以通过stat
命令查看文件的信息:
stat example.txt
1.3 inode的大小
inode也会消耗硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是inode区(inode table),存放inode所包含的信息。
每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。
查看每个硬盘分区的inode总数和已经使用的数量,可以使用df命令。
df -i
查看每个inode节点的大小,可以用如下命令:
sudo dumpe2fs -h /dev/hda | grep "Inode size"
由于每个文件都必须有一个inode,因此有可能发生inode已经用光,但是硬盘还未存满的情况。这时,就无法在硬盘上创建新文件。
1.4 inode号码
每个inode都有一个号码,操作系统用inode号码来识别不同的文件。
这里值得重复一遍,Unix/Linux系统内部不使用文件名,而使用inode号码来识别文件。对于系统来说,文件名只是inode号码便于识别的别称或者绰号。
表面上,用户通过文件名,打开文件。实际上,系统内部这个过程分成三步:首先,系统找到这个文件名对应的inode号码;其次,通过inode号码,获取inode信息;最后,根据inode信息,找到文件数据所在的block,读出数据。
使用ls -i命令,可以看到文件名对应的inode号码:
ls -i example.txt
1.5 目录文件
Unix/Linux系统中,目录(directory)也是一种文件。打开目录,实际上就是打开目录文件。
目录文件的结构非常简单,就是一系列目录项(dirent)的列表。每个目录项,由两部分组成:所包含文件的文件名,以及该文件名对应的inode号码。
理解了上面这些知识,就能理解目录的权限。目录文件的读权限(r)和写权限(w),都是针对目录文件本身。由于目录文件内只有文件名和inode号码,所以如果只有读权限,只能获取文件名,无法获取其他信息,因为其他信息都储存在inode节点中,而读取inode节点内的信息需要目录文件的执行权限(x)。
1.6 硬链接
一般情况下,文件名和inode号码是"一一对应"关系,每个inode号码对应一个文件名。但是,Unix/Linux系统允许,多个文件名指向同一个inode号码。
这意味着,可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访问。这种情况就被称为"硬链接"(hard link)。
ln命令可以创建硬链接:
ln 源文件 目标文件
源文件与目标文件的inode号码相同,都指向同一个inode。inode信息中有一项叫做"链接数",记录指向该inode的文件名总数,这时就会增加1。
反过来,删除一个文件名,就会使得inode节点中的"链接数"减1。当这个值减到0,表明没有文件名指向这个inode,系统就会回收这个inode号码,以及其所对应block区域。
这里顺便说一下目录文件的"链接数"。创建目录时,默认会生成两个目录项:“.“和”…”。前者的inode号码就是当前目录的inode号码,等同于当前目录的"硬链接";后者的inode号码就是当前目录的父目录的inode号码,等同于父目录的"硬链接"。所以,任何一个目录的"硬链接"总数,总是等于2加上它的子目录总数(含隐藏目录)。
1.7 软链接
除了硬链接以外,还有一种特殊情况。
文件A和文件B的inode号码虽然不一样,但是文件A的内容是文件B的路径。读取文件A时,系统会自动将访问者导向文件B。因此,无论打开哪一个文件,最终读取的都是文件B。这时,文件A就称为文件B的"软链接"(soft link)或者"符号链接(symbolic link)。
这意味着,文件A依赖于文件B而存在,如果删除了文件B,打开文件A就会报错:“No such file or directory”。这是软链接与硬链接最大的不同:文件A指向文件B的文件名,而不是文件B的inode号码,文件B的inode"链接数"不会因此发生变化。
ln -s命令可以创建软链接:
ln -s 源文件 目标文件
1.8 inode的特殊作用
由于inode号码与文件名分离,这种机制导致了一些Unix/Linux系统特有的现象。
-
- 有时,文件名包含特殊字符,无法正常删除。这时,直接删除inode节点,就能起到删除文件的作用。
-
- 移动文件或重命名文件,只是改变文件名,不影响inode号码。
-
- 打开一个文件以后,系统就以inode号码来识别这个文件,不再考虑文件名。因此,通常来说,系统无法从inode号码得知文件名。
第3点使得软件更新变得简单,可以在不关闭软件的情况下进行更新,不需要重启。因为系统通过inode号码,识别运行中的文件,不通过文件名。更新的时候,新版文件以同样的文件名,生成一个新的inode,不会影响到运行中的文件。等到下一次运行这个软件的时候,文件名就自动指向新版文件,旧版文件的inode则被回收。
二、Shell
2.1 Bash的模式扩展
hell 接收到用户输入的命令以后,会根据空格将用户的输入,拆分成一个个词元(token)。然后,Shell 会扩展词元里面的特殊字符,扩展完成后才会调用相应的命令。这种特殊字符的扩展,称为模式扩展(globbing)。其中有些用到通配符,又称为通配符扩展(wildcard expansion)。Bash 一共提供八种扩展。
-
- 波浪线扩展
~
: 自动扩展成当前用户的主目录
- 波浪线扩展
-
- 问号扩展
?
:代表文件路径里面的任意单个字符,不包括空字符
- 问号扩展
-
- 星号扩展
*
:代表文件路径里面的任意数量的任意字符,包括零个字符
- 星号扩展
-
- 方括号扩展
[]
:文件名匹配,即扩展后的结果必须符合现有的文件路径。如果不存在匹配,就会保持原样,不进行扩展。方括号扩展还有两种变体:[^...]
和[!...]
。它们表示匹配不在方括号里面的字符,这两种写法是等价的。
- 方括号扩展
-
- 方括号简写扩展
[a-z]
:表示匹配一个连续的范围
- 方括号简写扩展
-
- 大括号扩展
{}
:表示分别扩展成大括号里面的所有值,各个值之间使用逗号分隔。内部的逗号前后不能有空格,大括号也可以与其他模式联用,并且总是先于其他模式进行扩展
- 大括号扩展
-
- 大括号简写扩展
{a-z}
:表示扩展成一个连续序列
- 大括号简写扩展
-
- 变量扩展
$
:扩展成变量值
- 变量扩展
-
- 子命令扩展
$()
:可以将子命令的运行结果作为父命令的参数
- 子命令扩展
-
- 算术扩展
$(())
:扩展成整数运算的结果
- 算术扩展
-
- 字符类扩展
[[:class:]]
:扩展成某一类特定字符之中的一个
- 字符类扩展
-
•
[[:alnum:]]
:匹配任意英文字母与数字 -
•
[[:alpha:]]
:匹配任意英文字母 -
•
[[:blank:]]
:空格和 Tab 键。 -
•
[[:cntrl:]]
:ASCII 码 0-31 的不可打印字符。 -
•
[[:digit:]]
:匹配任意数字 0-9。 -
•
[[:graph:]]
:A-Z、a-z、0-9 和标点符号。 -
•
[[:lower:]]
:匹配任意小写字母 a-z。 -
•
[[:print:]]
:ASCII 码 32-127 的可打印字符。 -
•
[[:punct:]]
:标点符号(除了 A-Z、a-z、0-9 的可打印字符)。 -
•
[[:space:]]
:空格、Tab、LF(10)、VT(11)、FF(12)、CR(13)。 -
•
[[:upper:]]
:匹配任意大写字母 A-Z。 -
•
[[:xdigit:]]
:16进制字符(A-F、a-f、0-9)。 -
•
[![:digit:]]
:在第一个中括号之后加!
表示否定
-
- 先解释,再执行:Bash 接收到命令以后,发现里面有通配符,会进行通配符扩展,然后再执行命令
-
- 不匹配时,会原样输出:文件名扩展在没有可匹配的文件时,会原样输出
-
- 只适用于单层路径:
?
或*
这样的通配符,不能匹配路径分隔符(/
),Bash 4.0允许**
匹配零个或多个子目录
- 只适用于单层路径:
-
- 文件名可以使用通配符:文件名包括特殊字符,需要把文件名放在单引号里面
2.2 Here文档
Here 文档(here document)是一种输入多行字符串的方法,格式如下:
<< token text token
开始标记由两个小于号和文档名称组成,结束标记是顶格写的文档名称,中间的是文档内容。
Here 文档内部会发生变量替换,同时支持反斜杠转义,但是不支持通配符扩展,双引号和单引号也失去语法作用,变成了普通字符。如果不希望发生变量替换,可以把 Here 文档的开始标记放在单引号之中。
<< 'token' text token
Here 文档的本质是重定向,所以,Here 字符串只适合那些可以接受标准输入作为参数的命令,对于其他命令无效,比如echo
命令就不能用 Here 文档作为参数。
Here 文档还有一个变体,叫做 Here 字符串(Here string),使用三个小于号(<<<
)表示
# 对ddd进行md5计算 md5sum <<< 'ddd'
2.3 环境变量
环境变量
# 查看所有环境变量 env # or printenv # 查看指定环境变量 printenv PATH # or echo $PATH
自定义变量
自定义变量是用户在当前 Shell 里面自己定义的变量,一旦退出当前 Shell,该变量就不存在了。
set
命令可以显示所有变量(包括环境变量和自定义变量),以及所有的 Bash 函数。
创建变量:var=value
读取变量:$var
删除变量:unset var
输出变量:export var
/export var=value
用户创建的变量仅可用于当前 Shell,子 Shell 默认读取不到父 Shell 定义的变量。为了把变量传递给子 Shell,需要使用
export
命令。子 Shell 如果修改继承的变量,不会影响父 Shell.
特殊变量
$?
:上一个命令执行的退出码,0表示成功,非0表示失败
$$
:当前Shell的进程ID
$_
:上一条命令的最后一个参数,比如上一条命令ls a.txt b.txt c.txt
则$_
表示c.txt
$!
:最近一个后台执行的异步命令的进程 ID
$0
:当前 Shell 的名称(在命令行直接执行时)或者脚本名(在脚本中执行时)
$-
:当前 Shell 的启动参数
$#
/$@
:脚本的参数数量
变量默认值
以machine=elk01
为例
${machine:-elk01}
:如果变量machine存在且不为空,则返回它的值,否则返回elk01
${machine:=elk01}
:如果变量machine存在且不为空,则返回它的值,否则将它设为elk01,并且返回elk01
${machine:+elk01}
:如果变量名存在且不为空,则返回elk01,否则返回空值。目的是测试变量是否存在
${machine:?"变量machine未定义"}
:如果变量machine存在且不为空,则返回它的值,否则打印出变量machine未定义
并中断脚本的执行。
上面四种语法如果用在脚本中,变量名的部分可以用数字1
到9
,表示脚本的参数。
filename=${1:?"filename missing."}
declare命令
declare
命令可以声明一些特殊类型的变量,为变量设置一些限制,比如声明只读类型的变量和整数类型的变量。语法如下:
declare OPTION var=value
-a
:声明数组变量
-f
:输出所有函数定义
-F
:输出所有函数名
-i
:声明整数变量
-l
:声明变量为小写字母
-p
:查看变量信息
-r
:声明只读变量
-u
:声明变量为大写字母
-x
:变量输出为环境变量
declare
命令如果用在函数中,声明的变量只在函数内部有效,等同于local
命令。不带任何参数时,等同于不带有任何参数的set
命令。
let命令
let
命令声明变量时,可以直接执行算术表达式。
let
命令的参数表达式如果包含空格,就需要使用引号。
let
可以同时对多个变量赋值,赋值表达式之间使用空格分隔。
字符串
字符串长度
var=hello echo ${#var} # 5
子字符串
${var:offset:length} # offset 默认0 # length 默认字符串长度
用法和python的var[3:3]
的用发类似,省略了python的步长参数。同样也支持负数,但是表示的意义不一样。
$ foo="This string is long." echo ${foo: -5:-2} # lon
offset
为-5
,表示从倒数第5个字符开始截取,所以返回long.
。如果指定长度length
为2
,则返回lo
;如果length
为-2
,表示要排除从字符串末尾开始的2个字符,所以返回lon
。
搜索和替换
# 如果 pattern 匹配变量 variable 的开头, # 删除最短匹配(非贪婪匹配)的部分,返回剩余部分 ${variable#pattern} # 如果 pattern 匹配变量 variable 的开头, # 删除最长匹配(贪婪匹配)的部分,返回剩余部分 ${variable##pattern}
通过下面的方式可以截取文件路径的文件名
${path##*/} # */可以匹配路径前面的部分,删除后只剩下文件名
# 将头部匹配的部分,替换成其他内容 # 模式必须出现在字符串的开头 ${variable/#pattern/string}
上面是字符串头部匹配的模式,下面展示的字符串尾部匹配模式,用法与头部匹配一样
# 如果 pattern 匹配变量 variable 的结尾, # 删除最短匹配(非贪婪匹配)的部分,返回剩余部分 ${variable%pattern} # 如果 pattern 匹配变量 variable 的结尾, # 删除最长匹配(贪婪匹配)的部分,返回剩余部分 ${variable%%pattern}
还有任意位置的匹配模式
# 如果 pattern 匹配变量 variable 的一部分, # 最长匹配(贪婪匹配)的那部分被 string 替换,但仅替换第一个匹配 ${variable/pattern/string} # 如果 pattern 匹配变量 variable 的一部分, # 最长匹配(贪婪匹配)的那部分被 string 替换,所有匹配都替换 ${variable//pattern/string}
算术运算
算术表达式
((...))
会自动忽略内部的空格,但是这个语法不返回值,如果算术结果为0表示命令执行失败,所以如果执行(( 3 - 3 ))
命令行会提示失败。如果需要获取运算结果可以通过(( v = 1 + 1))
或者v=$((1 + 1))
(等号两边不能有空格)赋值给变量。
((...))
支持的运算符号:
-
•
+
:加号 -
•
-
:减号 -
•
*
:乘号 -
•
/
:整除 -
•
%
:余数 -
•
**
:指数 -
•
++
:自增 -
•
--
:自减
嵌套的写法:$(((5**2) * 3))
或者$(($((5**2)) * 3))
,$((...))
圆括号的变量可以省略$
符号。
进制
默认是十进制
-
•
0123
:八进制 -
•
0x123
:十六进制 -
•
base#10101
:base进制
位运算
$((...))
:支持的位运算:
-
•
<<
:向左位移 -
•
>>
:向右位移 -
•
&
:位与运算 -
•
|
:或运算 -
•
~
:否运算 -
•
^
:异或运算
逻辑运算
$((...))
:支持的逻辑运算:
-
•
<
:小于 -
•
>
:大于 -
•
<=
:小于等于 -
•
>=
:大于等于 -
•
==
:相等 -
•
!=
:不等于 -
•
&&
:逻辑与 -
•
||
:逻辑或 -
•
!
:逻辑否 -
•
exp ? a : b
:三元运算符
求值运算
,
在$(())
内部是求值运算符
`⚡ echo $((foo=2+4, 4*9)) 36 ⚡ echo $foo 6`
expr命令
expr
命令支持算术运算
expr 3 + 2 # 运算表达式中间需要空格
let命令
let x=2+3 # 运算表达式中间不能有空格
行操作
快捷键
-
•
Ctrl + a
:光标移动到行首 -
•
Ctrl + e
:光标移动到行尾 -
•
Ctrl + d
:删除光标位置的字符 -
•
Ctrl + w
:删除光标前面的单词 -
•
Ctrl + t
:光标位置的字符与它前面一位的字符交换位置 -
•
Alt + t
:光标位置的单词与它前面一位的单词交换位置 -
•
Alt + l
:将光标位置至词尾转为小写 -
•
Alt + u
:将光标位置至词尾转为大写 -
•
Ctrl + k
:剪切光标位置至行尾 -
•
Ctrl + u
:剪切光标位置至行首 -
•
Alt + d
:剪切光标位置到词尾 -
•
Alt + Backspace(删除键)
:剪切光标位置至词首 -
•
Ctrl + y
:在光标位置粘贴
历史记录
bash会保存用户的操作历史,保存在~/.bash_history
文件中,默认存储500条记录,环境变量HISTFILE
指向这个文件。
history
命令能显示操作历史,该命令会在所有命令前面加上编号,通过定制环境变量HISTTIMEFORMAT
可以显示每个历史操作的执行时间。
export HISTTIMEFORMAT='%F %T '
通过history
命令返回的历史记录和编号,可以通过!编号
的方式快速执行指定编号的命令。
history -c
可以清楚历史操作记录,设置export HISTSIZE=0
不保存本次操作历史。
!
感叹号命令的用途
-
•
!!
:执行上一条命令 -
•
!n
:执行历史文件里指定编号的命令 -
•
!-n
:执行当前命令之前的第n条命令⚡echo 1 1 ⚡ echo 2 2 ⚡ echo 3 3 ⚡ !-1 ⚡ echo 3 3
-
•
!string
:执行最近一条以指定字符串string开头的命令这种模式只能匹配命令,不能匹配参数由于这种模式会扩展成匹配历史操作,所以需要正常使用感叹号!
的时候需要转义一下 -
•
!?string
:执行最近一条包含指定字符串string的命令 -
•
^str1^str2
:执行最近一条包含str1的命令,并将其替换成str2
Ctrl + r
快捷键会进入一个交互模式,会根据输入查找开头匹配输入的历史命令
`⚡ echo 3 bck-i-search: e_`
只输入e
,会按最新的顺序查找立即操作中以e
开头的命令并展示在操作行中,回车即会执行。
脚本
脚本第一行通常是指定解释器,即这个脚本必须通过什么解释器执行。
#!/bin/sh # or #!/bin/bash # or #!/usr/bin/env bash # env命令在/usr/bin目录下,返回bash可执行文件的位置
如果没有第一行的Shebang,就不能直接通过./script.sh
的方式运行,必须通过将脚本传给解释器执行:
/bin/sh script.sh # or bash script.sh
参数
-
•
$0
:脚本文件名 -
•
$1
`$9`:对应第19个参数,第10个参数用${10}
引用 -
•
$#
:参数的总和 -
•
$@
:全部的参数,参数之间使用空格分隔 -
•
$*
:全部的参数,参数之间使用变量$TFS
值的第一个字符分隔,默认为空格
getopts命令
while getopts 'lha:' OPTION; do case "$OPTION" in l) echo "linuxconfig" ;; h) echo "h stands for h" ;; a) avalue="$OPTARG" echo "The value provided is $OPTARG" ;; ?) echo "script usage: $(basename $0) [-l] [-h] [-a somevalue]" >&2 exit 1 ;; esac done shift "$(($OPTIND - 1))"
-l
、-h
、-a
三个参数,只有-a
可以带参数值,所以a
的后面必须带有冒号,对于输入的lha
之外的命令将会匹配到?
,匹配到的参数中,$OPTARG
保存参数值,比如-a foo
中$OPTARG
保存的就是foo。
exit命令
exit
命令用于终止当前脚本的执行,并向 Shell 返回一个退出值。0
表示正常,1
表示发生错误,2
表示用法不对,126
表示不是可执行脚本,127
表示命令没有发现。如果脚本被信号N
终止,则退出值为128 + N
。简单来说,只要退出值非0,就认为执行出错。
exit
与return
命令的差别是,return
命令是函数的退出,并返回一个值给调用者,脚本依然执行。exit
是整个脚本的退出,如果在函数之中调用exit
,则退出函数,并终止脚本执行。
source命令
source
命令最大的特点是在当前 Shell 执行脚本,不像直接执行脚本时,会新建一个子 Shell。所以,source
命令执行脚本时,不需要export
变量。
source
命令的另一个用途,是在脚本内部加载外部库。
#!/bin/bash source ./lib.sh function_from_lib
source
有一个简写形式,可以使用一个点(.
)来表示。
$ . .bashrc
read命令
有时,脚本需要在执行过程中,由用户提供一部分数据,这时可以使用read
命令。
read [-options] [variable...]
如果用户的输入项少于read
命令给出的变量数目,那么额外的变量值为空。如果用户的输入项多于定义的变量,那么多余的输入项会包含到最后一个变量中。
#!/bin/bash echo Please, enter your firstname and lastname read FN LN echo "Hi! $LN, $FN !"
如果read
命令之后没有定义变量名,那么环境变量REPLY
会包含所有的输入。
#!/bin/bash # read-single: read multiple values into default variable echo -n "Enter one or more values > " read echo "REPLY = '$REPLY'"
read
命令除了读取键盘输入,可以用来读取文件。
#!/bin/bash filename='/etc/hosts' while read myline do echo "$myline" done < $filename
-t 参数
-t
参数可以设置超时秒数,如果超过了指定时间,用户仍然没有输入,脚本将放弃等待,继续向下执行。
-p 参数
-p
参数指定用户输入的提示信息。
-a 参数
-a
参数把用户的输入赋值给一个数组,从零号位置开始。
$ read -a people alice duchess dodo $ echo ${people[2]} dodo
-n 参数
-n
参数指定只读取若干个字符作为变量值,而不是整行读取。
-e 参数
-e
参数允许用户输入的时候,使用readline
库提供的快捷键,比如自动补全。
-d 参数
-d
参数定义用户输入的结束符,而不是回车符。
-s 参数
-s
参数使得用户的输入不显示在屏幕上,这常常用于输入密码或保密信息。
IFS变量
IFS(内部字段分隔符,Internal Field Separator 的缩写)默认值是空格、Tab 符号、换行符号,通常取第一个(即空格)。
比如读取/etc/passwd
文件内容
root:x:0:0:root:/root:/usr/bin/zsh daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
文件中每一行内容的字段由冒号(:
)分隔,如果需要将一行内容映射到各个字段变量中,指定IFS为冒号(:
)即可
# 假设行内文本读取到变量file_info中 IFS=":" read user pw uid gid name home shell <<< "$file_info"
IFS
的赋值命令和read
命令写在一行,这样的话,IFS
的改变仅对后面的命令生效,该命令执行后IFS
会自动恢复原来的值。如果不写在同一行,则需要在命令执行完之后恢复IFS为原来的值。
OLD_IFS="$IFS" IFS=":" read user pw uid gid name home shell <<< "$file_info" IFS="$OLD_IFS"
条件判断
if
if
条件判断的语法结构
if commands; then commands [elif commands; then commands...] [else commands] fi
if
和then
写在同一行时,需要分号分隔。分号是 Bash 的命令分隔符。它们也可以写成两行,这时不需要分号。*
if
后面可以跟任意数量的命令。这时,所有命令都会执行,但是判断真伪只看最后一个命令,即使前面所有命令都失败,只要最后一个命令返回0
,就会执行then
的部分。
test
test
命令的三种形式:
# 写法一 test expression # 写法二 方括号和表达式之间必须有括号,因为方括号本身是一条命令 [ expression ] # 写法三 支持正则判断 [[ expression ]]
参数 | 说明 | 备注 | 说明 | 备注 | 说明 |
---|---|---|---|---|---|
-a | 文件是否存在 | -b | 是否块文件 | -c | 是否字符设备 |
-d | 文件是否目录 | -e | 是否存在 | -h | 是否符号链接 |
-N | 自上次读取是否被修改 | -r | 是否可读 | -s | 是否不为空 |
-S | 是否socket | -w | 是否可写 | -x | 是否可执行 |
f1 -nt f2 | f1是否比f2新 | f1 -ot f2 | f1是否比f2旧 | f1 -ef f2 | 是否引用相同的inode |
-n | 字符串长度大于0 | -z | 字符串长度为0 | s1 = s2 | 字符串相等 |
s1 == s2 | 字符串相等 | s1 != s2 | 字符串不相等 | s1 ‘>’ s2 | 字典顺序s1在s2之后 |
-eq | 整数相等 | -ne | 整数不相等 | -le | 整数小于等于 |
-lt | 整数小于 | -ge | 整数大于等于 | -gt | 整数大于 |
=~ | 正则匹配 | ||||
case
case结构的语法:
case expression in pattern ) commands ;; pattern ) commands ;; ... esac
匹配模式:
-
•
a)
:匹配a
。 -
•
a|b)
:匹配a
或b
。 -
•
[[:alpha:]])
:匹配单个字母。 -
•
???)
:匹配3个字符的单词。 -
•
*.txt)
:匹配.txt
结尾。 -
•
*)
:匹配任意输入,通过作为case
结构的最后一个模式。
Bash 4.0之前,
case
结构只能匹配一个条件,然后就会退出case
结构。Bash 4.0之后,允许匹配多个条件,这时可以用;;&
终止每个条件块。
循环
while
while condition; do commands done
until
until condition; do commands done
until和while相反,while只要条件满足会一直执行,until是一旦条件满足就停止。
for…in
for variable in list; do commands done
for
for (( expression1; expression2; expression3 )); do commands done
break
break
命令立即终止循环,程序继续执行循环块之后的语句,即不再执行剩下的循环。
continue
continue
命令立即终止本轮循环,开始执行下一轮循环。
select
select
结构主要用来生成简单的菜单。它的语法与for...in
循环基本一致。
select name [in list] do commands done
举例
#!/bin/bash # select.sh select brand in Samsung Sony iphone symphony Walton do echo "You have chosen $brand" done
函数
函数的定义:
# 第一种 fn() { # codes } # 第二种 function fn() { # codes }
通过declare
命令查看当前Shell定义的所有函数: declare -f
(输出函数体)/declare -F
(不输出函数体),通过unset
命令删除函数:unset -f functionName
在函数体内通过参数变量$1
~`$9访问传入的第1-第9个参数,
0
‘
表示函数名,
‘
0`表示函数名,`
0‘表示函数名,‘#表示参数总数,
@
‘
表示全部参数,用空格分隔,
‘
@`表示全部参数,用空格分隔,`
@‘表示全部参数,用空格分隔,‘*`表示全部参数,用IFS的默认分隔符分隔。
局部变量
函数体内声明的变量属于全局变量,整个脚本都可以读取,通过local
关键字来声明变量为局部变量,仅在函数体内访问。
数组
数组的定义:
ARRAY=(value1 value2 ... valueN) # or ARRAY=( value1 value2 value3 ) # or 通过指定位置赋值 ARRAY=([2]=value3 [0]=value1 [1]=value1)
通过通配符赋值:
mp3s=( *.mp3 ) # 将当前目录的所有 MP3 文件,放进一个数组。
通过参数声明数组:
# 声明 declare -a ARRAYNAME # 将用户输入读入数组 read -a var
访问数组元素
array[0]=a # 不指定位置默认为0 # array=a echo ${array[0]}
@
和*
是特殊索引,返回数组的所有成员:${array[@]}
/${array[*]}
一般配合for...in
遍历数组,注:一般把${array[@]}
放在双引号之中
activities=( swimming "water skiing" canoeing "white-water rafting" surfing ) for act in ${activities[@]}; # <<-不加双引号会有问题 do echo "Activity: $act"; done
数组activities
实际包含5个元素,但是for...in
循环直接遍历${activities[@]}
,会导致返回7个结果。加上双引号可以避免这种情况。
拷贝数组
# 拷贝activities数组并添加一个元素 hobbies=( "${activities[@]}" diving )
数组长度
${#array[*]}
或者${#array[@]}
会返回数组的长度
非空位置
${!array[@]}
或者${!array[*]}
会返回数组中有值的元素位置
数组截取
${array[@]:pos:length}
从pos位置开始截取数组array长度为length的子数组返回,length省略则返回从pos开始的所有元素
数组拼接
+=
符号可以对数组进行追加:
foo=(a b c) foo+=(d e f) # foo = a b c d e f
如果是拼接两个数组遍历:
foo=(a b c) bar=(d e f) foo+=${bar[@]} # foo = ad e f b c
正确的拼接方法:
foo=(a b c) bar=(d e f) foo=(${foo[@]} ${bar[*]}) # @和*可以相互替换使用 # foo = a b c d e f
删除元素
unset
命令可以删除元素:
# 删除角标为2的元素 unset foo[2] # 删除数组 unset foo
关联数组
declare -A
声明关联数组,使用和其他语言里的hash或者map对象相似
declare -A colors colors["red"]="#ff0000" colors["green"]="#00ff00" colors["blue"]="#0000ff" echo ${colors["blue"]}
set
Bash 执行脚本时,会创建一个子 Shell,set
命令用来修改子Shell环境的运行参数。
set -u
Bash会忽略不存在的变量,而不是报错提醒,在脚本头部加上set -u
,后面遇到不存在的变量就会报错。
#!/usr/bin/env bash set -u # or # set -o nounset echo $a echo bar
即使脚本报错也不会影响脚本后面代码的执行,开发中需要在脚本报错后立即终止执行,放在造成严重的后果。通过command || exit 1
只要commad返回非0就立即停止执行脚本。
# 写法一 command || { echo "command failed"; exit 1; } # 写法二 if ! command; then echo "command failed"; exit 1; fi # 写法三 command if [ "$?" -ne 0 ]; then echo "command failed"; exit 1; fi
set -e
代替上面的冗余写法,只要脚本发生错误,就终止执行。set +e
表示关闭此功能,或者通过command || true
关闭,|| true
可以使该行命令始终返回true而不触发异常。
set -e # or # set -o errexit
set -o pipefail
set -e
有一个例外情况,就是不适用于管道命令。加上set -o pipefail
只要一个子命令失败,整个管道命令就失败,脚本就会终止执行。
set -x
用于调试复制的脚本,每行输出的行首如果有+
表示脚本的执行,在执行脚本时加上-x
参数(bash -x test.sh
)也有相同的效果
set -x # or # set -o xtrace set +x # 后续的脚本执行不再输出调试
常用写法:
# 写法一 set -Eeuxo pipefail # 写法二 set -Eeux set -o pipefail
shopt
shopt
作用和set
类似,区别是set
是从 Ksh 继承的,属于 POSIX 规范的一部分,而shopt
是 Bash 特有的。
临时文件
临时文件通常保存在/tmp
目录下,但是存在这一些问题,首先这个目录时所有用户都可以访问的,里面的文件所有用户都可以读;其次临时文件一般在脚本运行完之后需要删除,但是如果脚本意外退出会导致临时文件的堆积。
所以,应该使用mktemp
命令来创建临时文件,mktemp
命令生成的文件名是随机的,同时只有创建者有权读写,再通过配合使用trap
命令来保证脚本退出时临时文件被删除。
#!/bin/bash trap 'rm -f "$TMPFILE"' EXIT TMPFILE=$(mktemp) || exit 1 echo "Our temp file is $TMPFILE"
mktemp
命令的参数:
-
•
-d
:创建临时目录 -
•
-p
:指定临时文件的目录,默认值是环境变量$TMPDIR
的值 -
•
-t
:指定临时文件名的模板,X
表示随机字符,比如:mktemp -t mytmp.XXXXXXX
表示有7位随机字符
trap
命令格式:trap ACTION SIG1 SIG2
,其中ACTION表示bash命令,常用的信号有:
-
•
HUP
:编号1,脚本与所在的终端脱离联系。 -
•
INT
:编号2,用户按下 Ctrl + C,意图让脚本终止运行。 -
•
QUIT
:编号3,用户按下 Ctrl + 斜杠,意图退出脚本。 -
•
KILL
:编号9,该信号用于杀死进程。 -
•
TERM
:编号15,这是kill
命令发出的默认信号。 -
•
EXIT
:编号0,这不是系统信号,而是 Bash 脚本特有的信号,不管什么情况,只要退出脚本就会产生。
trap
命令一般放在脚本的开头位置,否则它上方的命令导致的退出都不会被捕获,多条ACTION命令可以通过函数封装:
function egress { command1 command2 command3 } trap egress EXIT
Session
登录Session
登录 Session 是用户登录系统以后,系统为用户开启的原始 Session,通常需要用户输入用户名和密码进行登录。
登录 Session 一般进行整个系统环境的初始化,启动的初始化脚本依次如下。
-
•
/etc/profile
:所有用户的全局配置脚本。 -
•
/etc/profile.d
目录里面所有.sh
文件 -
•
~/.bash_profile
:用户的个人配置脚本。如果该脚本存在,则执行完就不再往下执行。 -
•
~/.bash_login
:如果~/.bash_profile
没找到,则尝试执行这个脚本(C shell 的初始化脚本)。如果该脚本存在,则执行完就不再往下执行。 -
•
~/.profile
:如果~/.bash_profile
和~/.bash_login
都没找到,则尝试读取这个脚本(Bourne shell 和 Korn shell 的初始化脚本)。
==Linux 发行版更新的时候,会更新
/etc
里面的文件,比如/etc/profile
,因此不要直接修改这个文件。如果想修改所有用户的登陆环境,就在/etc/profile.d
目录里面新建.sh
脚本。==
如果只是修改个人的登录环境,一般是写在~/.bash_profile
里面。
非登录Session
非登录 Session 是用户进入系统以后,手动新建的 Session,这时不会进行环境初始化。比如,在命令行执行bash
命令,就会新建一个非登录 Session。
非登录 Session 的初始化脚本依次如下。
-
•
/etc/bash.bashrc
:对全体用户有效。 -
•
~/.bashrc
:仅对当前用户有效。
对用户来说,~/.bashrc
通常是最重要的脚本。非登录 Session 默认会执行它,而登录 Session 一般也会通过调用执行它。每次新建一个 Bash 窗口,就相当于新建一个非登录 Session,所以~/.bashrc
每次都会执行。
注意,执行脚本相当于新建一个非互动的 Bash 环境,但是这种情况不会调用
~/.bashrc
。
登出
~/.bash_logout
脚本在每次退出 Session 时执行,通常用来做一些清理工作和记录工作,比如删除临时文件,记录用户在本次 Session 花费的时间。
三、SSH
端口转发
SSH 除了登录服务器,还有一大用途,就是作为加密通信的中介,充当两台服务器之间的通信加密跳板,使得原本不加密的通信变成加密通信。这个功能称为端口转发(port forwarding),又称 SSH 隧道(tunnel)。
(1)将不加密的数据放在 SSH 安全连接里面传输,使得原本不安全的网络服务增加了安全性,比如通过端口转发访问 Telnet、FTP 等明文服务,数据传输就都会加密。
(2)作为数据通信的加密跳板,绕过网络防火墙。
端口转发有三种使用方法:动态转发,本地转发,远程转发。下面逐一介绍。
动态转发
动态转发指的是,本机与 SSH 服务器之间创建了一个加密连接,然后本机内部针对某个端口的通信,都通过这个加密连接转发。它的一个使用场景就是,访问所有外部网站,都通过 SSH 转发。
动态转发需要把本地端口绑定到 SSH 服务器。至于 SSH 服务器要去访问哪一个网站,完全是动态的,取决于原始通信,所以叫做动态转发。
$ ssh -D local-port tunnel-host -N
上面命令中,-D
表示动态转发,local-port
是本地端口,tunnel-host
是 SSH 服务器,-N
表示这个 SSH 连接只进行端口转发,不登录远程 Shell,不能执行远程命令,只能充当隧道。
注意,这种转发采用了 SOCKS5 协议。访问外部网站时,需要把 HTTP 请求转成 SOCKS5 协议,才能把本地端口的请求转发出去。
本地转发
本地转发(local forwarding)指的是,SSH 服务器作为中介的跳板机,建立本地计算机与特定目标网站之间的加密连接。本地转发是在本地计算机的 SSH 客户端建立的转发规则。
它会指定一个本地端口(local-port),所有发向那个端口的请求,都会转发到 SSH 跳板机(tunnel-host),然后 SSH 跳板机作为中介,将收到的请求发到目标服务器(target-host)的目标端口(target-port)。
$ ssh -L local-port:target-host:target-port tunnel-host
上面命令中,-L
参数表示本地转发,local-port
是本地端口,target-host
是你想要访问的目标服务器,target-port
是目标服务器的端口,tunnel-host
是 SSH 跳板机。
注意,本地端口转发采用 HTTP 协议,不用转成 SOCKS5 协议。
远程转发
远程端口指的是在远程 SSH 服务器建立的转发规则。
这种场景比较特殊,主要针对内网的情况。本地计算机在外网,SSH 跳板机和目标服务器都在内网,而且本地计算机无法访问内网之中的 SSH 跳板机,但是 SSH 跳板机可以访问本机计算机。
由于本机无法访问内网 SSH 跳板机,就无法从外网发起 SSH 隧道,建立端口转发。必须反过来,从 SSH 跳板机发起隧道,建立端口转发,这时就形成了远程端口转发。
$ ssh -R local-port:target-host:target-port -N local
上面的命令,首先需要注意,不是在本机执行的,而是在 SSH 跳板机执行的,从跳板机去连接本地计算机。-R
参数表示远程端口转发,local-port
是本地计算机的端口,target-host
和target-port
是目标服务器及其端口,local
是本地计算机。
远程端口转发要求本地计算机也安装了 SSH 服务器,这样才能接受 SSH 跳板机的远程登录。
rsync
rsync 是一个常用的 Linux 应用程序,用于文件同步。
它可以在本地计算机与远程计算机之间,或者两个本地目录之间同步文件(但不支持两台远程计算机之间的同步)。它也可以当作文件复制工具,替代cp
和mv
命令。
它名称里面的r
指的是 remote,rsync 其实就是“远程同步”(remote sync)的意思。与其他文件传输工具(如 FTP 或 scp)不同,rsync 的最大特点是会检查发送方和接收方已有的文件,仅传输有变动的部分(默认规则是文件大小或修改时间有变动)。
常用参数:
-
•
-r
:递归目录 -
•
-a
:可以替代-r
,除了可以递归同步以外,还可以同步元信息(比如修改时间、权限等)。 -
•
-n
:模拟执行结果,并不真正执行命令。 -
•
--delete
:rsync 只确保源目录的所有内容(明确排除的文件除外)都复制到目标目录。它不会使两个目录保持相同,并且不会删除文件。如果要使得目标目录成为源目录的镜像副本,则必须使用--delete
参数,这将删除只存在于目标目录、不存在于源目录的文件。 -
•
--exclude
:指定排除模式,可以多次使用。 -
•
--include
:指定必须同步的文件模式。 -
•
-i
:参数表示输出源目录与目标目录之间文件差异的详细情况。 -
•
--ignore-existing
:参数表示只要该文件在目标目录中已经存在,就跳过去,不再同步这些文件。 -
•
-m
:参数指定不同步空目录。 -
•
-P
参数是--progress
和--partial
这两个参数的结合。 -
•
--partial
参数允许恢复中断的传输。不使用该参数时,rsync
会删除传输到一半被打断的文件;使用该参数后,传输到一半的文件也会同步到目标目录,下次同步时再恢复中断的传输。一般需要与--append
或--append-verify
配合使用。 -
•
--progress
参数表示显示进展。 -
•
-z
参数指定同步时压缩数据。---------------------------END---------------------------
题外话
“不是只有程序员才要学编程?!”
认真查了一下招聘网站,发现它其实早已变成一项全民的基本技能了。
连国企都纷纷要求大家学Python!
世界飞速发展,互联网、大数据冲击着一切,各行各业对数据分析能力的要求越来越高,这便是工资差距的原因,学习编程顺应了时代的潮流。
在这个大数据时代,从来没有哪一种语言可以像Python一样,在自动化办公、爬虫、数据分析等领域都有众多应用。
更没有哪一种语言,语法如此简洁易读,消除了普通人对于“编程”这一行为的恐惧,从小学生到老奶奶都可以学会。
《2020年职场学习趋势报告》显示,在2020年最受欢迎的技能排行榜,Python排在第一。
它的角色类似于现在Office,成了进入职场的第一项必备技能。
如果你也想增强自己的竞争力,分一笔时代的红利,我的建议是,少加点班,把时间腾出来,去学一学Python。
因为,被誉为“未来十年的职场红利”的Python,赚钱、省钱、找工作、升职加薪简直无所不能!
目前,Python人才需求增速高达**174%,人才缺口高达50万,**部分领域如人工智能、大数据开发, 年薪30万都招不到人!
感兴趣的小伙伴,赠送全套Python学习资料,包含面试题、简历资料等具体看下方。
👉优快云大礼包🎁:全网最全《Python学习资料》免费赠送🆓!(安全链接,放心点击)

一、Python所有方向的学习路线
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。
二、Python必备开发工具
工具都帮大家整理好了,安装就可直接上手!
三、最新Python学习笔记
当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。
四、Python视频合集
观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
五、实战案例
纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
六、面试宝典
简历模板
