Shell 入门

最后更新:
阅读次数:

本文所有 shell 脚本均通过 bash 解释器执行

*.sh 文件

  • .sh 文件(eg:1.sh)
#!/bin/bash

echo 'hello world!'

#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell(bash、zsh、sh、csh 等)。

使用 cat /etc/shells 命令即可查看当前系统下已安装了几种 shell 解释器。

  • 两种执行方式
    • 将 *.sh 文件变为可执行文件: 先 chmod +x file.sh,然后 ./file.sh
    • 直接使用解释器执行 *.sh 文件:/bin/bash file.sh
  • 不同 shell 解释器的解析语法可能会有差异(比如下面的例子)
  • 不同系统下的 shell 语法也可能存在差异(比如 Linux 的 expr 1+2 与 Mac 的 $((1+2))
shell_array=(12 21 33 "aaa")

echo ${shell_array[3]}
# 使用不同的 shell 解释器执行上面的代码

$ /bin/bash arr.sh # aaa
$ /bin/zsh arr.sh # 33
$ /bin/sh arr.sh # aaa

shell 变量

  • shell 变量的注意事项
    • 声明变量时,变量名和等号之间不能有空格(注意)
    • 使用变量的方法有下面的两种
# 使用方法一
shell_a=123

echo $shell_a

# 使用方法二(推荐使用)
bbb='cool shell'

echo ${bbb}
  • 使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变
readonly readOnlyA='I am readonly'

echo ${readOnlyA}
  • 使用 unset 命令可以删除变量,但不能删除只读变量
shell_a='lalala'

unset shell_a

echo ${shell_a}

shell 变量类型

  • 局部变量

局部变量在脚本或命令中定义,仅在当前 shell 实例中有效,其他 shell 启动的程序不能访问局部变量。

  • 环境变量

所有的程序,包括 shell 启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候 shell 脚本也可以定义环境变量。

$PWD      # 当前目录路径
$USER # 当前用户的用户名
$HOME # 指定用户的主工作目录
$PATH # 指定命令的搜索路径
$SHELL # 指当前用户用的是哪种 Shell
# 环境变量相关的操作

echo # 显示某个环境变量的值 echo $PATH
export # 设置一个新的环境变量 export HELLO="hello" (可以无引号)
env # 显示所有环境变量
set # 显示本地定义的shell变量
unset # 清除环境变量 unset HELLO
readonly # 设置只读环境变量 readonly HELLO

shell 字符串

  • shell 的双引号可以进行变量替换
a=123

echo '$a' # $a
echo "$a" # 123
echo "${a}" # 123


# 下面有几种变量替换的更高级的用法(a为一个变量,b为一个值)

# 如果变量 a 为空或已被删除(unset),那么返回值 b,但不改变 a 的值
echo ${a:-b}

# 如果变量 a 为空或已被删除(unset),那么返回值 b,并将 a 的值设置为 b
echo ${a:=b}
  • shell 的反引号可以进行命令替换(即先执行命令,然后返回执行命令后的结果)
echo `date`   # 2017年 8月31日 星期四 01时02分10秒 CST
echo `pwd` # /Users/percy507

# 也可以用 $(pwd) 来代替 `pwd`
echo $(pwd) # /Users/percy507/Documents/blog
# 格式化输出日期(2017-11-15@10:27:18)

curdate="`date +%Y-%m-%d@%H:%M:%S`"
echo $curdate
  • 获取字符串长度:${#变量名}
string='ab_cd'

echo ${#string} # 5
  • 提取子字符串:${变量名:起始位置:长度}
    • 起始位置由 0 开始
string='ab_cd'

echo ${string:1:3} # b_c
  • 删除匹配的子字符串,并返回新字符串(不会改变原字符串)
    • ${变量名#子串} 从字符串左边开始匹配,删除最短匹配子串
    • ${变量名##子串} 从字符串左边开始匹配,删除最长匹配子串
    • ${变量名%子串} 从字符串右边开始匹配,删除最短匹配子串
    • ${变量名%%子串} 从字符串右边开始匹配,删除最长匹配子串
string="aacabc def gggga"

echo ${string#*g} # ggga
echo ${string##*g} # a
echo ${string%c*a} # aacab
echo ${string%%c*a} # aa

echo ${string} # aacabc def gggga
  • 字符串替换,并返回新字符串(不会改变原字符串)
    • ${变量名/查找/替换值} 替换第一个匹配的结果
    • ${变量名//查找/替换值} 替换所有匹配的结果
string="abc def ggg"

echo ${string/g/\$} # abc def gg
echo ${string//g/\$} # abc def $$$

echo $string # abc def ggg
  • 字符串拼接
aaa='hello '
bbb='shell~'

echo ${aaa}${bbb} # hello shell~
  • 获取子字符串的位置
string="abc def ggg"
substring="ef"

strindex() {
x="${1%%$2*}"
[[ "$x" = "$1" ]] && echo -1 || echo "${#x}"
}

strindex "$string" "$substring" # 输出 5

shell 数组

bash 支持一维数组(不支持多维数组),并且没有限定数组的大小。

  • 获取数组的长度:${#变量名[@]}
shell_array=(12 21 33 "aaa")

echo ${#shell_array[@]} # 4
  • 根据索引获取数组元素:${变量名[索引]}
# arr.sh
shell_array=(12 21 33 "aaa")

echo ${shell_array[3]} # aaa
  • 输出数组的所有元素:${变量名[@]}${变量名[*]}
shell_array=(12 21 33 "aaa")

echo ${shell_array[@]} # 12 21 33 aaa
echo ${shell_array[*]} # 12 21 33 aaa

向 shell 脚本传递参数

  • 使用命令执行下面的代码: /bin/bash test.sh 参数1 参数2 参数3
# test.sh
echo "Shell 传递参数实例!";
echo "执行的文件名:$0"; # 输出 test.sh
echo "第一个参数为:$1"; # 输出 参数1
echo "第二个参数为:$2"; # 输出 参数2
echo "第三个参数为:$3"; # 输出 参数3

# $# 传递到脚本的参数的个数
echo $#; # 输出 3

# $@ 或 $* 输出 shell 脚本的所有参数
echo $* # 输出 参数1 参数2 参数3
echo $@ # 输出 参数1 参数2 参数3

shell 基本运算符

算术运算符

The expr syntax in mac OS is $((1+2)) , not expr 1+2

echo $((3 + 2))   # 输出 5
echo $((3 - 2)) # 输出 1
echo $((3 * 2)) # 输出 6
echo $((3 / 2)) # 输出 1
echo $((3 % 2)) # 输出 1
echo $((3 == 2)) # 输出 0
echo $((3 != 2)) # 输出 1

关系运算符

  • 关系运算符只支持数字,不支持字符串,除非字符串的值是数字
a=20
b=20
# -eq 等于
if [ $a -eq $b ]
then
echo "$a -eq $b: a 等于 b"
else
echo "$a -eq $b: a 不等于 b"
fi

# == 等于
if (($a == $b))
then
echo "(($a == $b)): a 等于 b"
else
echo "(($a == $b)): a 不等于 b"
fi
# -gt 大于
if [ $a -gt $b ]
then
echo "$a -eq $b: a 大于 b"
else
echo "$a -eq $b: a 小于等于 b"
fi

# > 大于
# -lt 小于
if [ $a -lt $b ]
then
echo "$a -eq $b: a 小于 b"
else
echo "$a -eq $b: a 大于等于 b"
fi

# < 小于

布尔运算符

# !  非运算
# -o 或运算
# -a 与运算

if [ 200 -lt 100 -o 200 -gt 15 ]
then
echo "200 -lt 100 -o 200 -gt 15 : 返回 true"
else
echo "200 -lt 100 -o 200 -gt 15 : 返回 false"
fi

if [ 200 -lt 100 -a 200 -gt 15 ]
then
echo "200 -lt 100 -a 200 -gt 15 : 返回 true"
else
echo "200 -lt 100 -a 200 -gt 15 : 返回 false"
fi

逻辑运算符

# &&  逻辑的 AND
# || 逻辑的 OR

if [[ 100 < 100 || 100 == 100 ]]
then
echo "返回 true"
else
echo "返回 false"
fi

上面的布尔运算符和逻辑运算符看的有点懵,可以看看下面的这个链接。

Rule of thumb: Use -a and -o inside square brackets, && and || outside.

It’s important to understand the difference between shell syntax and the syntax of the [ command.

  • && and || are shell operators. They are used to combine the results of two commands. Because they are shell syntax, they have special syntactical significance and cannot be used as arguments to commands.
  • [ is not special syntax. It’s actually a command with the name [, also known as test. Since [ is just a regular command, it uses -a and -o for its and and or operators. It can’t use && and || because those are shell syntax that commands don’t get to see.

But wait! Bash has a fancier test syntax in the form of [[ ]]. If you use double square brackets, you get access to things like regexes and wildcards. You can also use shell operators like &&,||, <, and > freely inside the brackets because, unlike [, the double bracketed form is special shell syntax. Bash parses [[ itself so you can write things like [[ $foo == 5 && $bar == 6 ]].

字符串运算符

# [$str] 检测字符串是否为空,为空返回 false
# [$str1 = $str2] 检测两个字符串是否相等,相等返回 true

a="abc"
b="efg"

if [ $a = $b ]
then
echo "$a = $b : a 等于 b"
else
echo "$a = $b: a 不等于 b"
fi

文件测试运算符

# [ -d $file ]  检测文件是否是目录,如果是,则返回 true
# [ -f $file ] 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true
# [ -r $file ] 检测文件是否可读,如果是,则返回 true
# [ -w $file ] 检测文件是否可写,如果是,则返回 true
# [ -x $file ] 检测文件是否可执行,如果是,则返回 true
# [ -s $file ] 检测文件是否为空(文件大小是否大于0),不为空返回 true
# [ -e $file ] 检测文件(包括目录)是否存在,如果是,则返回 true
if [ -e ./1.sh ]
then
echo '文件 1.sh 存在'
else
echo '文件 1.sh 不存在'
fi

常用命令

echo "Hello, Shell"          # 输出 Hello, Shell
printf "Hello, Shell\n" # 输出 Hello, Shell
test # 用于检查某个条件是否成立

流程控制

分支选择语句

  • if-else 语句
    • 注意 if 后面的条件语句的方括号与其内容必须用空格隔开,否则会报错(比如不能写为 [$a -eq 111],而应该写为 [ $a -eq 111 ]
a=111

if [ $a -eq 111 ]
then
echo $a 等于 100
fi

if [ $a -eq 100 ]
then
echo $a 等于 100
elif [ $a -lt 100 ]
then
echo $a 小于 100
else
echo $a 大于 100
fi
  • case 语句
    • shell 的 case 语句与其他语言中的 switch … case 语句类似
a=12
b=13

case $a in
11)
echo 'a equal 12'
;;
$b)
echo 'a equal b'
;;
*)
echo 'a equal nothing'
;;
esac

循环语句(for、while)

  • for 语句
# 遍历数组

for item in www baidu com
do
echo "Current item is $item"
done

# Current item is www
# Current item is baidu
# Current item is com
# 显示主目录下以 .bash 开头的文件路径

for filePath in $HOME/.bash*
do
echo $filePath
done
  • while 语句
count=3

while (($count <= 5))
do
echo "Current count = $count"
let count++;
done

# Current count = 3
# Current count = 4
# Current count = 5

continue、break 语句

a=0

while (($a<10))
do
a=$(($a + 1))

if [ $a -eq 5 ]
then
continue # 或者 break
fi

echo $a
done

shell 函数

  • 语法
# 方括号中的内容表示可选
[function] func(){
# do something
[return 9]
}
  • 函数需要先定义后使用,调用函数只需要给出函数名,不需要加括号
function demoFun(){
echo "这是我的第一个 shell 函数!"
}

echo "-----函数开始执行-----"
demoFun
echo "-----函数执行完毕-----"

# -----函数开始执行-----
# 这是我的第一个 shell 函数!
# -----函数执行完毕-----
  • 使用 $? 来接收函数的返回值
    • 注意:shell 函数 return 的值是有限制的(返回值必须是整数,最大返回 255,超过 255,则从 0 开始计算)
function demoFun(){
echo "这是我的第一个 shell 函数!"
return 100
}

demoFun
result=$?
echo $result
  • 向函数传递参数
function demoFun(){
echo "参数1: ${1}"
echo "参数2: ${2}"
}

demoFun 123 "aaa"

输入输出的重定向

Unix 命令默认从标准输入设备(stdin)获取输入,将结果输出到标准输出设备(stdout)显示。一般情况下,标准输入设备就是键盘,标准输出设备就是终端界面,即显示器。

命令的输出不仅可以是显示器,还可以很容易地将输出转移到文件,这被称为输出重定向。

# 将 pwd 命令返回的数据输入到 pwd.txt 文件中
# 若 pwd.txt 文件已存在,则会用新的数据覆盖旧的数据
# 若 pwd.txt 文件不存在,则会自动新建该文件

pwd > pwd.txt


# 若你不希望文件的内容被覆盖,而是希望将新的内容追加到文件末尾,则可以使用 >> 符号

pwd >> pwd.txt

和输出重定向一样,Unix 命令也可以从文件获取输入(输入重定向)。

# 使用 sort 命令排序

cat 1.txt
12
3
4
99
55
13

sort -n < 1.txt
3
4
12
13
55
99