Shell Scripting Tutorial

482 阅读4分钟

01 - 介绍

查看操作系统当前支持的 Shell

cat /etc/shells

显示以下结果:

/bin/bash  
/bin/csh
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh

/bin/sh 是最早的 Shell ,仍然在 unix 和 unix-like 的操作系统上使用。 其中 /bin/bash 就是我们要学习的,它是 /bin/sh 的升级版本,直观且灵活,目前是标准的 GNU Shell ,大部分的 Shell 可运行在 Linux 和 Mac 操作系统上运行。

看看 bash 在哪

which bash

返回:

/bin/bash

创建第一个 Shell Script

执行以下命令,会在当前目录下创建一个文件 01-introduction.sh ,这个文件的后缀是 .sh 其实没有这个后缀我们的脚本也能正常运行,但是为了规范并方便 IDE 识别,一般都会使用该后缀。

touch 01-introduction.sh

我们修改这个文件为以下内容:

#! /bin/bash  
echo "hello,world"  

第一行: #! /bin/bash 其实就是我们刚才查看的bash位置,让解释器知道这是什么脚本;
第二行: echo "hello,world" 在控制台输出。如果输出的字符是变量名使用单引号可原样输出,使用 echo -e "hello,world\n" 会开启转义模式,会正确识别 \n

第一行其实也可以输入别的开头,具体可见 What is the difference between “#!/usr/bin/env bash” and “#!/usr/bin/bash”?

在当前目录下执行这个脚本:

./01-introduction.sh

提示 zsh: permission denied: ./01-introduction.sh 原因是通过 touch 创建的文件只能读写,没有可执行权限。我们 chmod +x 01-introduction.sh 增加执行权限后,再次执行 ./01-introduction.sh 发现成功出现了 hello,world

其实这里也可以直接使用 sh 01-introduction.sh 来执行,不会出现权限的问题。

02 - 使用变量和注释

上次我们学习了如何创建一个最简单的脚本,这节来学习下如何使用变量和注释。还是以上次的脚本为例。

先来看注释

#! /bin/bash  

# 我是单行注释

<< COMMENT
 我是多行注释,注意:使用相同的开头和结尾
COMMENT

echo "hello,world"

<< COMMENT2
你好啊
COMMENT2

echo "hello,world"

执行这段脚本后,输出

hello,world
hello,world

可以看到,单行注释使用 # ,多行注释使用 << 后面在带一个字符,只要前后一致闭合,之间包含的内容 Shell 就认为是注释。

内置变量和自定义

为了方便说明,以下的输出全部以注释的形式展示:

echo $BASH       #/bin/sh
echo $BASH_VERSION #3.2.57(1)-release
echo $HOME #/Users/pleuvoir
echo $PWD #/Users/pleuvoir/dev/space/shell-tutorial

name="pleuvoir"  #注意:变量名和值=之间不要有空格,否则无法识别
echo my name is $name  #my name is pleuvoir

1age=18  #特别注意:变量名称不能以数字开头,否则无法识别
echo $1age  #age 预期18,但是这里输出的是age

03 - 接收用户输入

换行输入

# 输入框会换行
echo "Enter your username:"
read username
echo "your name is $username"

输入框会换行,一次可以输入多个

echo "Enter your usernames:"
read username1 username2  username3
echo "username1=$username1,username2=$username2,username3=$username3"

输入框不换行

read -p "enter your new name:" newname_val
echo "newname=$newname_val"

安静输入,不显示输入内容

read -sp "enter your pwd :" newpwd_val
echo "newpwd_val=$newpwd_val"

同样也可以在一行中接收多个内容

echo "enter names:"
read -a  infos_val
echo "name1=${infos_val[0]},name2=${infos_val[1]},name3=${infos_val[2]}"

没有定义变量名,则可以使用内置的REPLY

read -p "hello:"
echo $REPLY

04 - 解析命令行参数

# 原生的,第一个参数是脚本文件名
echo $0 $1 $2 '$0 $1 $2'  #输出 01-introduction.sh 1 2 $0 $1 $2 

# 使用数组(推荐使用)
args=($@) 
echo "${args[0]},${args[1]},${args[2]}" #1,2,3 
# 输出所有值 sh 01-introduction.sh 1 2 3 则输出 1 2 3
echo $@ #1 2 3
echo $#  #传入参数的个数,一个也没有为0

05 - 条件语句

基本语法

if [[ condition ]]; then
 #statements
elif [[ condition ]]; then
 #statements
fi

运算符

Integer 类型

-eq    等于
-ne    不等于
-gt    大于
-ge    大于等于
-lt    小于
<
<=
>
>=

字符串类型

=
==    推荐使用
!=
<    比较 ASCII
>    比较 ASCII
-z   长度为0返回true
-n   字符串不为空(长度大于0)返回true

示例

#判断字符串为空
empty=
if [[ -z $empty ]]; then
 echo "empty is null"
fi

empty2=""  
if [[ -z $empty2 ]]; then
 echo "empty2 is null"
fi

empty2=" " #注意这里加了空格就不是空了
if [[ ! -z $empty2 ]]; then
 echo "empty2 is not null"
fi

# 判断字符串的情况
inputnum="b"
if [[ $inputnum == "a" ]]; then
 echo "inputnum is a"
elif [[ $inputnum == "b" ]]; then
 echo "inputnum is b"
else 
 echo "inputnum is else number"
fi

# 判断数字的情况
word=10
if [[ $word -eq 10 ]]; then
 echo "word is 10"
fi

需求:字符串如果为空则报错,可以通过 ! -n 或者 -z

条件判断表达式

# 与
uage=29

if [[ $uage -gt 15  &&  $uage -lt 23 ]]; then
 echo "valid age is $uage"
else
 echo "oh my god."
fi

#或
if [[ $uage -gt 50  ||  $uage -lt 35 ]]; then
 echo "OR valid age is $uage"
else
 echo "OR oh my god."
fi

#分开写也是可以的
if [[ $uage -gt 50   ]] || [[ $uage -lt 35 ]]; then
 echo "OR valid age is $uage"
else
 echo "OR oh my god."
fi

#case 
vehicle=bike1

case $vehicle in
 "car" )
  echo "car";;
 "bike" )
  echo "bike";;
 "ship" )
  echo "ship";; 
 * )
   echo "unknown"
esac

06 - 文件操作

检查文件是否存在

echo "current pwd is $PWD"
read -p "Enter filename:" filename
if [[ -e $filename ]]; then  #注意这个-e是文件运算当文件存在返回true,还有很多其他的可以参考运算符大全
 echo "this file $filename exist"
else
 echo "this file $filename not exist"
fi

这里其实没什么好说的,就是判断文件存不存在。关键点就在 -e 这个操作上,而这只是运算符中的一种,大家需要什么命令可以先去检索一下。

追加写文件

echo "current pwd is $PWD"
read -p "Enter filename:" filename
if [[ -e $filename ]]; then
 echo "this file $filename exist"
 if [[ -w $filename ]]; then
  echo "type something text, quit ctrl+d"
  cat >> $filename   #>>是追加写文件,在最后一个字符后追加,不会换行;>是全覆盖写
 else
  echo "no write permission"  #可以通过执行 chmod -w filename 把写权限去掉
 fi
else
 echo "this file $filename not exist"
fi

07 - 数学运算

a=10
b=2

echo "a+b=$((a+b))"
echo "a-b=$((a-b))"
echo "a*b=$((a*b))"
echo "a/b=$((a/b))"
echo "a%b=$((a%b))"

08 - 数组

test=('windows' 'ubuntu' 'linux')
test[3]="mac"

unset test[2] #移除

echo "${test[@]}" #全部
echo "${!test[@]}" #索引
echo "${test[0]}" #windows

#也可以把字符串当做数组
strings="hwjklrnjw"
echo "${strings[@]}"
echo "${strings[0]}" #整个就是0

09 - 循环

args=0
while [[ $args -lt 10 ]]; do
 echo "$args"
 (( ++args ))
  sleep 2
done

# 类似do while
until [[ $args -gt 5 ]]; do
 echo "$args"
 (( ++args ))
 sleep 2
done

#fori
for (( i = 0; i < 10; i++ )); do
 if [[ $i%2 -eq 0 ]]; then
  echo "mod 2 zero"
  continue #可以continue也可以break
 fi
 echo "$i"
done

#打印当前目录下所有内容
for item in *; do
 echo $i
done

# 列表循环
for i in {1,5,10}; do
 echo "$i"
done

10 - 函数

name=pleuvoir

function print(){
 echo "out_name is $name"
 local name=$1 #注意这里要加上local 否则会被认为是全局变量
 echo "local_name is $name"
}

echo "out_name is $name"
print "hehe" #传递参数进去
echo "out_name is $name"


#判断文件是否存在的示例
usage(){
 echo "输入文件位置"
 echo "$0 filename"
}

is_file_exits(){
 local file=$1
 echo "file is $file"
 [[ -f $file ]] && return 0 || return 1 #三目运算符
}

[[ $# -eq 0 ]] && usage  #当没有传递参数时调用帮助方法


if ( is_file_exits $1 ); then
 echo "$1 存在"
else
 echo "$1 不存在"
fi

11 - 监听Signal

echo "PID is $"
# 可以监听多个信号
trap "echo hehe;exit 0" 15 2

for (( i = 0; i < 10; i++ )); do
 echo $i
 sleep 10
done
exit 0

附录

各种括号的作用()、(())、[]、[[]]、{}
运算符大全