02 命令和语法
类型 | 作用域 | 声明方式 | 规范 |
---|---|---|---|
自定义变量 | 当前shell | =(隐式声明) | 字符串、整型、浮点型、日期型 |
环境变量 | 当前shell及其子shell | export、declare -x(显示声明) | |
系统环境变量 | 所有shell | 启动加载 |
自定义变量
#!/bin/bash
# 这是脚本文件的shebang,它告诉系统脚本文件使用哪种解释器来执行。在这个例子中,使用的是bash解释器
#变量名=变量值(等号左右不能有空格),定义了两个变量page_size和page_num,并分别给它们赋值为1和2
page size=1
page_num=2
#将命令复制给变量,这样可以通过变量名来执行命令。
_ls=ls
#将命令结果赋值给变量,可以通过变量名来访问命令结果
file_list=$(ls -a)
#默认字符串,不会进行 + 运算。所以total的值为"page_size*page_num"字符串
total=page_size*page_num
#声明变量为整型(-i),后续的数值计算中,total会被当作整型来处理
let total=page_size*page_num
declare -i total=page_size*page_num
#导出环境变量,使得其在子进程中也可用
export total
declare -x total
-
declare [+/-] 选项 变量
选项 含义 - 给变量设定类型属性 + 取消变量的类型属性 -a 将变量声明为数组类型 -i 将变量声明为整数型 -x 将变量声明为环境变量 -r 将变量声明为只读变量 -p 显示指定变量的被声明的类型
系统环境变量
变量名 | 含义 | 常见操作 |
---|---|---|
$0 | 当前shell名称/脚本名称 | 2等可以获取到传入参数 |
$# | 传入脚本的参数数量 | if[ $# -gt 1 ] |
$* | 传入脚本的所有参数 | |
$? | 上条命令执行的状态码 | if [ $? -eq 0 ]; |
$PS1 | 命令提示符 | export PS1="\u@\h \w" (对应当前的主机名、用户名、目录) |
$HOME | 用户主文件夹(主目录) | cd ~ |
$PATH | 全局命令的搜索路径 | PATH=$PATH:[新增路径] |
$#
表示当前脚本或函数的参数个数。在shell脚本中,可以通过$#
来获取当前脚本或函数的参数个数,并根据参数个数执行相应的操作。$*
表示当前脚本或函数的所有参数列表。在shell脚本中,可以通过$*
来获取当前脚本或函数的所有参数,并根据参数列表执行相应的操作。$?
表示上一个命令的退出状态码。在shell脚本中,可以通过$?
来获取上一个命令的退出状态码,并根据状态码执行相应的操作。$PS1
表示shell提示符的格式。在shell中,可以通过修改$PS1
来自定义shell提示符的格式,以适应个人习惯或需求。$HOME
表示当前用户的主目录。在shell脚本中,可以通过$HOME
来获取当前用户的主目录,并根据主目录执行相应的操作。$PATH
表示系统的可执行文件路径列表。在shell中,可以通过修改$PATH
来添加或删除系统的可执行文件路径,以便更方便地执行系统命令或自定义命令。
配置文件加载
运算符和引用
使用示例:
# 在一个终端启动node服务
$ node server.js
Server running at http://127.0.0.1:8888/
# 在另一个终端里访问该服务
$ curl http://127.0.0.1:8888/
Hello World
# 但是在第一个终端里按 Ctrl+C 中断后,就无法访问了
# 在一个终端启动node服务,并使用符号 & 让命令在后台运行
$ node server.js &
[1] 15446
# 在另一个终端里访问该服务
$ curl http://127.0.0.1:8888/
Hello World
# 现在即使在第一个终端里按 Ctrl+C 也不会中断运行,但是如果将终端关闭,服务依旧会停止
# 如果想要关闭终端但是后台依旧运行,可以搭配 nohub
$ nohub node server.js &
管道
-
管道与管道符 | ,作用是将前一个命令的结果传递给后面的命令
-
语法:
cmd1 | cmd2
-
要求:管道右侧的命令必须能接受标准输入才行,比如 grep 命令,ls、mv等不能直接使用,但可以使用 xargs 预处理
注意:管道命令仅仅处理 stdout(标准输出),对于 stderr(标准错误输出) 会予以忽略,可以在脚本的前面使用
set -o pipefail
设置 shell 遇到管道错误退出 -
使用示例:
#!/bin/bash #该命令将文件platform.access.log的内容通过管道符|传递给grep命令,用于查找文件中包含字符串ERROR的行。这个命令的作用是在platform.access.log文件中查找错误信息 cat platform.access.log | grep ERROR #该命令使用netstat命令来显示所有的网络连接状态,并通过管道符|将结果传递给grep命令,用于查找所有处于ESTABLISHED状态的连接。最后,通过管道符|将结果传递给wc -l命令,用于统计行数。这个命令的作用是统计当前系统中所有处于ESTABLISHED状态的网络连接数量 netstat -an | grep ESTABLISHED | wc -l #该命令使用find命令在当前目录中查找所有扩展名为.sh的文件,并通过管道符|将结果传递给xargs命令,将查找到的文件名作为参数传递给ls -l命令,用于显示这些文件的详细信息。这个命令的作用是列出当前目录中所有扩展名为.sh的文件的详细信息 find . -maxdepth l -name "*.sh" | xargs ls -l
重定向
-
Shell 命令在执行的时候会有三个文件描述符 fd0、fd1、fd2,分别表示标准输入、标准输出和标准错误输出。默认这三个文件描述符会指向终端的输入、输出,重定向可以修改这种默认的引用关系。
-
比如将 fd0 指向某一个文件,fd1 和 fd2 指向某一个文件,这里就分为了输入重定向和输出重定向。
>
:将命令的输出重定向到一个文件中,如果文件不存在则创建,如果文件已经存在则覆盖。>>
:将命令的输出重定向到一个文件中,如果文件不存在则创建,如果文件已经存在则在文件末尾追加。2>
:将命令的错误输出重定向到一个文件中,如果文件不存在则创建,如果文件已经存在则覆盖。&>
:将命令的输出和错误输出重定向到一个文件中,如果文件不存在则创建,如果文件已经存在则覆盖。<
:将一个文件的内容作为命令的输入。<<
:将一段字符串作为命令的输入。
使用示例:
# 将 ls -l 的执行结果保存到 list.txt 文件中,同时将错误输出也保存到这个文件当中
$ ls -l >> list.txt 2>&1
# 这里是一个循环代码。循环的条件是从标准输入读取信息,然后将这个标准输入使用输入重构定向到刚才的 list.txt 文件,这样 read 会从文件当中一行一行读取,然后通过对每一行的信息做一个 cut 操作,取第一列保存到 auth.txt 文件当中
$ while read -r line; do
echo $line | cut -d " " -f 1 | xargs >> auth.txt
done < ./list.txt
# 执行之后可以看一下 auth.txt 文件
$ cat auth.txt
# 两个小于号 << 这样的输入重定向,它代表继续沿用当前的标准输入,暗示当识别到指令符号的时候,就会停止接受,然后将已经收到的结果传递给命令
$ wc -l <<EOF
heredoc> a
heredoc> b
heredoc> c
heredoc> d
heredoc> EOF
4
# 上面我们输入a、b、c、d,当输入EOF的时候就会停止输入,同时将已经接受到的四行传递给wc,然后wc统计出来4
以下是其他例子:
ls > file.txt
:将ls
命令的输出重定向到file.txt
文件中,如果文件不存在则创建,如果文件已经存在则覆盖。ls >> file.txt
:将ls
命令的输出重定向到file.txt
文件中,如果文件不存在则创建,如果文件已经存在则在文件末尾追加。ls 2> error.txt
:将ls
命令的错误输出重定向到error.txt
文件中,如果文件不存在则创建,如果文件已经存在则覆盖。ls &> output.txt
:将ls
命令的输出和错误输出重定向到output.txt
文件中,如果文件不存在则创建,如果文件已经存在则覆盖。sort < file.txt
:将file.txt
文件的内容作为sort
命令的输入。cat << EOF
:将下面的一段字符串作为cat
命令的输入直到遇到EOF
为止。
判断命令
shell中提供了test
、[
、[[
三种判断符号,可用于:
- 整数测试
- 字符串测试
- 文件测试
语法:
test condition
[ condition ]
[[ condition ]]
#!/bin/bash
# 整数测试
test $nl -eq $n2 # 测试 $nl 是否等于 $n2
test $nl -lt $n2 # 测试 $nl 是否小于 $n2
test $nl -gt $n2 # 测试 $nl 是否大于 $n2
# 字符串测试
test -z $str_a # 测试 $str_a 是否为空
test -n $str_a # 测试 $str_a 是否非空
test $str_a = $str_b # 测试 $str_a 是否等于 $str_b
# 文件测试
test -e /dmt && echo "exist" # 测试 /dmt 是否存在,并在存在时输出 "exist"
test -f /usr/bin/npm && echo "file exist" # 测试 /usr/bin/npm 是否存在,并在存在时输出 "file exist"
#!/bin/bash
name="hello world"
[ $name == "hello" ]
# 在判断上面两个变量是否相等的时候会报错
# 这是因为 $name 在命令中会被解析为 [ hello world == "hello" ]
# 所以我们最好使用双引号将变量包裹起来 [ "$name" == "hello" ] 这样就会被正常地解析为 [ "hello world" == "hello" ]
script.sh: line 5: [: too many arguments
Exited with error status 2
注意:
- 中括号前后要有空格符
- [ 和test是命令,只能使用自己支持的标志位;<、>、=只能用来比较字符串
- 中括号内的变量,最好都是用引号括起来
- [[更丰富,在整型比较中支持<、>、=,在字符串比较中支持=~正则
分支语句
在shell中,分支语句主要有if语句和case语句
if 语句
-
语法:其中 condition 表示判断条件,如果有多个判断条件的话可以使用
elif
表示elseif
,然后是else
程序段,最后是fi
结尾。if condition ; then 程序段 elif condition ; then 程序段 else 程序段 fi
-
示例:首先判断 level 是否非空字符串,如果非空的话再判断是否等于0或1
#!/bin/bash level=0 if [ -n "$level" ]; then if [ "$level" == 0 ]; then prefix=ERROR elif [ "$level" == 1 ]; then prefix=INFO else echo "log level not supported" fi fi echo "[${prefix}] $message"
-
示例:下方是一个连续判断的例子,我们可以在方括号内部使用
-o
、-e
表示或
、与
的关系,我们也可以将两个判断拆开然后用或操作符||
来连接。#!/bin/bash read -p "please inpout (Y/N) : " yn if [ "$yn" == "y" -o "$yn" == "Y" ]; then echo "ok continue" fi if [ "$yn" == "y" || "$yn" == "Y" ]; then echo "ok continue too" fi
case 语句
-
语法:
case $变量 in: "第一个变量内容") 程序段 ;; "第一个变量内容") 程序段 ;; *) 程序段 ;; esac
-
示例:
#!/bin/bash name=join case $name in "nick") echo "hi nick" ;; "join") echo "my name is john" ;; *) echo "404" ;; esac
循环
while 循环
-
表示条件成立的时候执行循环
-
语法:
while condition ; do 程序段; done
-
示例:
#!/bin/bash let num=0 while [ $num -lt 10 ]; do echo "current idx: $num" ((num++)) done
until 循环
-
表示条件成立的时候跳出循环
-
语法:
until condition ; do 程序段; done
-
示例:
#!/bin/bash let num=0 until [ $num -gt 10 ]; do echo "current idx: $num" ((num++)) done
for 循环
-
语法:
for var in [words...]; do 程序段; done
-
示例:
#!/bin/bash # 写法1,对列表内容进行循环 for foo in a b c; do echo $foo done # 写法2,数值方式循环 for((i=0;i<10;i++)) do echo $i done
函数
- 语法一:
funcName(){ echo "abc"; }
- 语法二:
function funcName() { echo "abc"; }
示例:
#!/bin/bash
printName() {
if [ $# -lt 2 ]; then
echo "illegal parameter."
exit 1
fi
echo "firstname is :$1"
echo "lastname is: $2"
}
# 引用函数,后面跟的是两个参数
printName jacky chen
注意点:
- shell自上而下执行,函数必须在使用前定义
- 函数获取变量和 shell script 类似,
$0
代表函数名,后续参数通过$1
、$2
.. 获取 - 函数内
return
仅仅表示函数执行状态,不代表函数执行结果 - 返回结果一般使用
echo
、printf
,在外面使用$()
、反引号
获取结果 - 如果没有
return
,函数状态是上一条命令的执行状态,存储在$?
中
示例:
#!/bin/bash
function test() { # 定义一个名为test的函数
local word="hello world" # 定义一个名为word的本地变量,值为"hello world"
echo $word # 输出word的值到标准输出
return 10 # 返回值为10
unset word # 没有执行到这里,因为前面已经执行了return语句
} # 函数定义结束
content=`test` # 执行test函数,并将输出赋值给变量content
echo "状态码:$?" # 打印上一条命令的状态码,应该是10
echo "执行结果:$content" # 打印变量content的值,即"hello world"
模块化
模块化的原理是在当前shell内执行函数文件,方式:source [函数库的路径]
#!/bin/bash
# add 函数
# @return platForm
function add() {
declare -i res=$1+$2
echo $res
}
#!/bin/bash
sourse './math.sh'
total=$(add 1 2)
echo $total
常用命令
使用案例:
# 对日志文件搜索出错误日志
cat cloudfun.log | grep -n "ERROR"
# 对日志文件搜索出错误日志,-t " " 表示按照空格分割,-k 3 代表用分割后的第三列的值去排序
cat cloudfun.log | grep -n "ERROR" | sort -t " " -k 3
# 查看错误日志前后的上下文,这里的-A和-B代表 after 和 before
grep "ERROR" cloudfun.log -A3 -B3
# head 和 tail 比较类似,代表查看一个文件的前n行/后n行
tail -n 10 cloudfun.log
# 标志位 -f 表示读取到文件的末尾之后不会停止命令的执行,会继续等待文件的输入,一般在排查问题的时候经常用到
tail -n 10 -f cloudfun.log