龙空技术网

前端学习 linux - shell 编程

小小小网安 390

前言:

而今大家对“ubuntu sh”大约比较珍视,朋友们都需要剖析一些“ubuntu sh”的相关文章。那么小编在网摘上收集了一些对于“ubuntu sh””的相关知识,希望姐妹们能喜欢,姐妹们一起来了解一下吧!

shell 原意是“外壳”,与 kernel(内核)相对应,比喻内核外的一层,是用户和内核沟通的桥梁。shell 有很多种,国内通常使用 bash。

第一个 shell 脚本

创建 hello-world.sh 文件,内容如下:

test11@pjl:~/tmp$ vim hello-world.sh
#!/bin/bashecho 'hello world'

第一行指定 shell 的类型:

test11@pjl:~/tmp$ echo $SHELL/bin/bash

Tip:通常约定以 sh 结尾。提前剧透:

test11@pjl:~/tmp$ sh hello-world.xxhello world

执行 sh 文件,提示权限不够:

test11@pjl:~/tmp$ ./hello-world.sh-bash: ./hello-world.sh: 权限不够
test11@pjl:~/tmp$ ll-rw-rw-r-- 1 test11 test11   31 6月  17 16:18 hello-world.sh

增加可执行权限:

test11@pjl:~/tmp$ chmod u+x hello-world.shtest11@pjl:~/tmp$ ll# hello-world.sh 变绿了-rwxrw-r-- 1 test11 test11   31 6月  17 16:18 hello-world.sh*

使用相对路径方式再次执行即可:

test11@pjl:~/tmp$ ./hello-world.shhello world

也可以使用绝对路径执行:

test11@pjl:~/tmp$ /home/test11/tmp/hello-world.shhello world

通过 sh xx.sh 无需可执行权限也可以执行。

Tip:下文还会使用 bash xx.sh 的执行方式。

首先删除可执行权限:

test11@pjl:~/tmp$ chmod u-x hello-world.shtest11@pjl:~/tmp$ ll总用量 20-rw-rw-r-- 1 test11 test11   31 6月  17 16:18 hello-world.sh
test11@pjl:~/tmp$ sh hello-world.shhello world
shell 注释单行注释:# 内容多行注释:
:<<!内容1内容2...内容N!
变量系统变量

例如 $SHELL 就是系统变量:

test11@pjl:~/tmp$ echo $SHELL/bin/bash

可以通过 set 查看系统变量。例如过滤 SHELL 系统变量:

test11@pjl:~/tmp$ set |more |grep SHELLSHELL=/bin/bashSHELLOPTS=braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor
自定义变量

定义变量age并输出:

test11@pjl:~/tmp$ vim demo.shtest11@pjl:~/tmp$ sh demo.shage=18age=18

内容如下:

test11@pjl:~/tmp$ cat demo.sh#!/bin/bashage=18echo age=$ageecho "age=$age"

注:1. 定义变量不要在等号前后加空格;2. 使用变量要使用 $;3. 最后两行输出效果相同

# `age=18` 改为 `age= 18`test11@pjl:~/tmp$ sh demo.shdemo.sh: 2: 18: not foundage=age=

使用 unset 可以销毁变量。请看示例:

test11@pjl:~/tmp$ vim demo.shtest11@pjl:~/tmp$ sh demo.shage=18age=# 脚本内容test11@pjl:~/tmp$ cat demo.sh#!/bin/bashage=18echo age=$ageunset ageecho age=$age

注:销毁变量 age 后再使用该变量,没有报错。

通过 readonly 定义静态变量,不能 unset。请看示例:

test11@pjl:~/tmp$ vim demo.shtest11@pjl:~/tmp$ sh demo.shage=18demo.sh: 4: unset: age: is read onlytest11@pjl:~/tmp$ cat demo.sh#!/bin/bashreadonly age=18echo age=$ageunset age

变量定义规则:

字母数字下划线,不能以数字开头等号两侧不能有空格变量名习惯大写

可以将命令运行结果赋予变量。请看示例:

命令 date

test11@pjl:~/tmp$ date2022年 06月 17日 星期五 16:52:57 CST
test11@pjl:~/tmp$ vim demo.shtest11@pjl:~/tmp$ sh demo.shdate1=2022年 06月 17日 星期五 16:54:02 CSTdate2=2022年 06月 17日 星期五 16:54:02 CST
test11@pjl:~/tmp$ cat demo.sh#!/bin/bashdate1=`date`date2=$(date)echo date1=$date1echo date2=$date2
环境变量

比如我在多个 .sh 文件中需要使用一个公共的变量,这时就可以使用环境变量,或称之为全局变量

环境变量通过 export=value 定义在 /etc/profile 文件中。请看第 30 行:

test11@pjl:~$ cat -n /etc/profile     1  # /etc/profile: system-wide .profile file for the Bourne shell (sh(1))     2  # and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).     3     4  if [ "${PS1-}" ]; then     5    if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then     6      # The file bash.bashrc already sets the default PS1.     7      # PS1='\h:\w\$ '     8      if [ -f /etc/bash.bashrc ]; then     9        . /etc/bash.bashrc    10      fi    11    else    12      if [ "`id -u`" -eq 0 ]; then    13        PS1='# '    14      else    15        PS1='$ '    16      fi    17    fi    18  fi    19    20  if [ -d /etc/profile.d ]; then    21    for i in /etc/profile.d/*.sh; do    22      if [ -r $i ]; then    23        . $i    24      fi    25    done    26    unset i    27  fi    28    29    30  export ANDROID_HOME=/home/pjl/software/android-studio-2021.1.1.22-linux/android-studio/bin    31  export PATH=$PATH:$ANDROID_HOME

这里定义了一个环境变量 ANDROID_HOME,将其输出看一下:

test11@pjl:~$ echo $ANDROID_HOME/home/pjl/software/android-studio-2021.1.1.22-linux/android-studio/bin

现在我们定义一个环境变量 EVN-VAR-TEST=pjl

# 查看文件最后两行root@pjl:/home/test11# tail -2 /etc/profileexport PATH=$PATH:$ANDROID_HOMEexport EVN_VAR_TEST=pjl

新的环境变量需要执行 source 才能立即生效。请看实例:

# 新的环境变量未生效root@pjl:/home/test11# echo $EVN_VAR_TEST# 修改后的配置信息立即生效root@pjl:/home/test11# source /etc/profile# 新的环境变量已生效root@pjl:/home/test11# echo $EVN_VAR_TESTpjl

尝试在 demo.sh 中使用新增环境变量。请看示例:

test11@pjl:~/tmp$ cat demo.sh#!/bin/bash:<<!date1=`date`date2=$(date)echo date1=$date1echo date2=$date2!echo env_var_test=$EVN_VAR_TEST

运行 demo.sh,发现变量为空,让配置立即生效即可:

test11@pjl:~/tmp$ sh demo.shenv_var_test=test11@pjl:~/tmp$ echo $EVN_VAR_TESTtest11@pjl:~/tmp$ source /etc/profile
test11@pjl:~/tmp$ echo $EVN_VAR_TESTpjltest11@pjl:~/tmp$ sh demo.shenv_var_test=pjl

注:笔者以 root 用户新增环境变量,并让配置生效,接着切换到 test11 用户,需要再次让配置生效。

位置参数变量

请先看示例:

test11@pjl:~/tmp$ sh demo.sh 100 200demo.sh 100 200100 200100 2002
test11@pjl:~/tmp$ cat demo.sh#!/bin/bashecho $0 $1 $2echo $*echo $@echo $#

语法介绍:

$0 - 命令本身$1 - 第一个参数。第10个参数需要写成 ${10}$* - 命令行中所有参数。所有参数看做一个整体$@ - 命令行中所有参数。把每个参数区分对待$# - 参数个数预定义变量

shell设计者预先定义变量,可以在 shell 脚本中直接使用。

Tip:用得不多,仅做了解。

语法介绍:

$$ - 当前进程的进程号

$! - 后台运行的最后一个进程的进程号

$? - 最后一次执行的命名的返回状态。0 表示执行成功。

请看示例:

test11@pjl:~/tmp$ sh demo.sh29174test11@pjl:~/tmp$ cat demo.sh#!/bin/bashecho $$
运算符

请看示例:

test11@pjl:~/tmp$ ./demo.sh 1 8v1=18v2=18v3=18v4=9
test11@pjl:~/tmp$ cat -n demo.sh     1  #!/bin/bash     2  v1=$(((1+8)*2))     3  echo v1=$v1     4  # 推荐     5  v2=$[(1+8)*2]     6  echo v2=$v2     7     8  tmp=`expr 1 + 8`     9  v3=`expr $tmp \* 2`    10  echo v3=$v3    11    12  v4=$[$1+$2]    13  echo v4=$v4

语法介绍:

有三种运算的方式:$((运算符))$[运算式]expr a + b。推荐使用 $[]expr 运算符需要有空格,例如 expr 1+8 就没有空格,而且乘号需要加一个转义符 \*Syntax error: "(" unexpected

test11@pjl:~/tmp$ sh demo2.shdemo2.sh: 2: Syntax error: "(" unexpectedtest11@pjl:~/tmp$ cat -n demo2.sh     1  #!/bin/bash     2  v1=$[(1+8)*2]     3  echo v1=$v1

据网友介绍:ubuntu 上 sh 命令默认是指向 dash,而不是 bash。dash 比 bash 更轻量,只支持基本的 shell 功能,有些语法不识别。可以直接用 bash a.sh,或者./a.sh 来执行脚本。

改为 bash./ 的方式执行,确实可以。请看示例:

test11@pjl:~/tmp$ bash demo2.shv1=18
if

语法有点怪,先看示例:

test11@pjl:~/tmp$ sh demo2.shabc 等于 abc100 大于等于 99存在 /home/test11/tmp/demo2.sh
test11@pjl:~/tmp$ cat -n demo2.sh     1  #!/bin/bash     2  if [ 'abc'='abc' ]     3  then     4          echo 'abc 等于 abc'     5  fi     6     7  if [ 100 -ge 99 ]     8  then     9          echo '100 大于等于 99'    10  fi    11    12  if [ -f /home/test11/tmp/demo2.sh ]    13  then    14          echo '存在 /home/test11/tmp/demo2.sh'    15  fi

语法介绍:

if 判断使用 [ 条件 ] 语法,[] 前后要有空格字符串比较用 =。非空返回 true数字比较:-lt 小于、-le 小于等于、-eq 等于、-gt 大于、-ge 大于等于、-ne 不等于文件权限进行判断:-r 有读的权限、-w 有写的权限、-e 有执行的权限文件类型进行判断:-f 文件存在且是一个常规文件、-e 文件存在、-d 文件存在并是一个目录

注:[] 前后要有空格,否则会报错。请看示例:

test11@pjl:~/tmp$ sh demo2.shdemo2.sh: 2: []: not foundtest11@pjl:~/tmp$ cat demo2.sh#!/bin/bashif []then        echo '空字符'fi

增加一个空格,由于空字符是假值,所以不会有输出:

test11@pjl:~/tmp$ sh demo2.shtest11@pjl:~/tmp$ cat demo2.sh#!/bin/bashif [ ]then        echo '空字符'fi
elseif

请看示例:

test11@pjl:~/tmp$ sh demo2.sh dog狗,100块test11@pjl:~/tmp$ sh demo2.sh cat猫,102块
test11@pjl:~/tmp$ cat demo2.sh#!/bin/bash# echo 参数1=$1if [ $1 = "dog" ]then        echo '狗,100块'elif [ $1 = "cat" ]then        echo '猫,102块'fi

类似前端的 if...elseif...elseif

case

请看示例,如果传参是 dog ,输出 '狗':

test11@pjl:~/tmp$ sh demo2.sh dog狗test11@pjl:~/tmp$ sh demo2.sh cat猫test11@pjl:~/tmp$ sh demo2.sh xx其他动物test11@pjl:~/tmp$ cat demo2.sh#!/bin/bashcase $1 in"dog")echo '狗';;"cat")echo '猫';;*)echo '其他动物';;esac

语法介绍:

case $变量名 in"值1")变量的值等于“值1”,执行程序1;;"值2")变量的值等于“值2”,执行程序2;;*)都不满足,执行;;esac
循环for
// 具体的几个值for i in v1 v2 v3 ...do     程序done

以下示例演示了 $@$* 的区别:

test11@pjl:~/tmp$ sh demo2.sh a b cnum1=anum1=bnum1=cnum2=a b c
test11@pjl:~/tmp$ cat demo2.sh#!/bin/bashfor i in "$@"do        echo num1=$idonefor i in "$*"do        echo  num2=$idone

你有几个参数,$@ 就把你当做几个;$* 只会把你当做一个整体;

语法二

for (( 初始值;循环控制条件;变量变化))do     程序done

请看示例:

test11@pjl:~/tmp$ bash demo2.sh 1001 到 100 的和:5050
test11@pjl:~/tmp$ cat -n demo2.sh     1  #!/bin/bash     2     3  sum=0     4  for(( i=1; i <= $1; i++))     5  do     6          sum=$[$sum+$i]     7  done     8  echo 1 到 $1 的和:$sum

注:第6行不要写成 $sum=$[$sum+$i]

while

请看示例:

test11@pjl:~/tmp$ bash demo2.sh 1001 到 100 的和:5050
test11@pjl:~/tmp$ cat -n demo2.sh     1  #!/bin/bash     2     3  sum=0     4  i=1     5     6  # while  [ $i <= $1 ]     7  while  [ $i -le $1 ]     8  do     9          sum=$[$sum+$i]    10          i=$[$i+1]    11  done    12    13  echo 1 到 $1 的和:$sum

假如把第 6 行放开,报错如下:

test11@pjl:~/tmp$ bash demo2.sh 100demo2.sh: 行 6: =: 没有那个文件或目录1 到 100 的和:0

语法介绍:

while [ 条件判断 ]do     程序done

Tip:while[ 之间有空格;条件判断] 有空格。例如删除一个空格就会报错 while[ $i -le $1 ]

test11@pjl:~/tmp$ bash demo2.sh 100demo2.sh: 行 6: while[ 1 -le 100 ]:未找到命令demo2.sh: 行 7: 未预期的符号“do”附近有语法错误demo2.sh: 行 7: `do'
read 获取控制台输入
test11@pjl:~/tmp$ sh demo2.sh请输入你个名字:

程序会阻塞,当你输入后会继续执行:

test11@pjl:~/tmp$ sh demo2.sh请输入你个名字:pjlname=pjl

通过 -t 参数能指定等待时间(秒),例如 5 秒内如果没有输入,程序会继续执行:

test11@pjl:~/tmp$ bash demo2.sh请输入你个名字:name=test11@pjl:~/tmp$ cat demo2.sh#!/bin/bashread -t 5 -p 请输入你个名字: name

语法:read 选项 参数

test11@pjl:~/tmp$ read -h-bash: read: -h:无效选项read:用法: read [-ers] [-a 数组] [-d 分隔符] [-i 缓冲区文字] [-n 读取字符数] [-N 读取字符数] [-p 提示符] [-t 超时] [-u 文件描述符] [名称 ...]test11@pjl:~/tmp$ read --helpread: read [-ers] [-a 数组] [-d 分隔符] [-i 缓冲区文字] [-n 读取字符数] [-N 读取字符数] [-p 提示符] [-t 超时] [-u 文件描述符] [名称 ...]    从标准输入读取一行并将其分为不同的域。    从标准输入读取单独的一行,或者如果使用了 -u 选项,从文件描述符 FD 中读取。    该行被分割成域,如同词语分割一样,并且第一个词被赋值给第一个 NAME 变量,第二    个词被赋值给第二个 NAME 变量,如此继续,直到剩下所有的词被赋值给最后一个 NAME    变量。只有 $IFS 变量中的字符被认作是词语分隔符。    如果没有提供 NAME 变量,则读取的行被存放在 REPLY 变量中。    选项:      -a array  将词语赋值给 ARRAY 数组变量的序列下标成员,从零开始      -d delim  持续读取直到读入 DELIM 变量中的第一个字符,而不是换行符      -e        使用 Readline 获取行      -i text   使用 TEXT 文本作为 Readline 的初始文字      -n nchars 读取 nchars 个字符之后返回,而不是等到读取换行符。                但是分隔符仍然有效,如果遇到分隔符之前读取了不足 nchars 个字符。      -N nchars 在准确读取了 nchars 个字符之后返回,除非遇到文件结束符或者读超时,                任何的分隔符都被忽略      -p prompt 在尝试读取之前输出 PROMPT 提示符并且不带                换行符      -r        不允许反斜杠转义任何字符      -s        不回显终端的任何输入      -t timeout        如果在 TIMEOUT 秒内没有读取一个完整的行则超时并且返回失败。                TMOUT 变量的值是默认的超时时间。TIMEOUT 可以是小数。                如果 TIMEOUT 是 0,那么仅当在指定的文件描述符上输入有效的时候,                read 才返回成功;否则它将立刻返回而不尝试读取任何数据。                如果超过了超时时间,则返回状态码大于 128      -u fd     从文件描述符 FD 中读取,而不是标准输入    退出状态:    返回码为零,除非遇到了文件结束符、读超时(且返回码不大于128)、    出现了变量赋值错误或者无效的文件描述符作为参数传递给了 -u 选项。
函数

请看示例:

test11@pjl:~/tmp$ bash demo2.shsum=300
test11@pjl:~/tmp$ cat demo2.sh#!/bin/bash# 定义函数function sum() {        # 第一个参数为 $1        sum=$[$1+$2]        echo sum=$sum}# 执行函数sum 100 200

语法介绍:

[ function ] funname [()]{    action;    [return int;]}
系统函数

shell 中也有系统函数。我们介绍两个抛砖引玉一下:

basename,返回文件名dirname,返回路径

test11@pjl:~/tmp$ basename /a/b/c/a.txta.txttest11@pjl:~/tmp$ basename /a/b/c/a.txt .txtatest11@pjl:~/tmp$ dirname /a/b/c/a.txt/a/b/c
shell 综合练习

需求:每天凌晨 3 点备份数据库。

实现如下:

假如 test.txt 就是我们备份完成的数据库:

root@pjl:/home/test11/tmp# lsbackup_database.sh  test.txt

执行三次写好的备份数据库的脚本:

root@pjl:/home/test11/tmp# bash backup_database.shDATETIME=2022-06-21_2009032022-06-21_200903/2022-06-21_200903/2022-06-21_200903.txt.gzroot@pjl:/home/test11/tmp# bash backup_database.shDATETIME=2022-06-21_2009042022-06-21_200904/2022-06-21_200904/2022-06-21_200904.txt.gzroot@pjl:/home/test11/tmp# bash backup_database.shDATETIME=2022-06-21_2009052022-06-21_200905/2022-06-21_200905/2022-06-21_200905.txt.gz

我们需要将数据备份到 /data/backup/db/ 目录中,现已生成3个备份:

root@pjl:/home/test11/tmp# ls /data/backup/db/2022-06-21_200903.tar.gz  2022-06-21_200904.tar.gz  2022-06-21_200905.tar.gz

最后看一下备份脚本内容:

root@pjl:/home/test11/tmp# cat -n backup_database.sh     1  #!/bin/bash     2     3  # 将数据备份到这 db 目录     4  BACKDIR=/data/backup/db     5     6  # 当前时间     7  # 输出:DATETIME=2022-06-21_110318     8  DATETIME=$(date +%Y-%m-%d_%H%M%S)     9  echo DATETIME=$DATETIME    10    11  # 创建备份目录。如果不存在,则创建    12  [ ! -d "${BACKDIR}/${DATETIME}" ] && mkdir -p "${BACKDIR}/${DATETIME}"    13    14  # 备份数据。读取 text.txt 传给 gzip 压缩,在重定向到指定目录    15  cat test.txt | gzip > ${BACKDIR}/${DATETIME}/$DATETIME.txt.gz    16    17  # 将文件处理成 tar.gz    18  cd ${BACKDIR}    19  tar -zcvf $DATETIME.tar.gz ${DATETIME}    20    21  # 删除对应的备份目录    22  rm -rf ${BACKDIR}/${DATETIME}

Tip:${}通常用于划定变量名的边界,例如:

test11@pjl:~$ a=1test11@pjl:~$ aa=2test11@pjl:~$ echo $aa2test11@pjl:~$ echo ${a}a1atest11@pjl:~$ echo "${a}a"1a

标签: #ubuntu sh