AcWing「Linux基础课」第 3 讲 Shell 语法

概论

shell 是我们通过命令行和操作系统沟通的语言

shell 脚本可以在命令行中执行,shell 是一种解释性语言,不需要编译。

常见的 shell 有:

  • bash(默认)

  • zsh

  • fish

shell 文件开头需要指明 shell 解释器,如果是 bash,就写 #! /bin/bash

运行方式

  • 解释器执行:不需要加权限,bash test.sh
  • 可执行文件运行:
    • 添加可执行权限:chmod +x test.sh
    • 执行:./test.sh

注释

单行注释#

多行注释

1
2
3
:<<EOF
内容
:EOF

其中EOF可以换成其它任意字符串。例如:abc / !

变量

定义变量

定义变量不需要加 $,变量默认都是字符串,默认值是空串 "",如果用到数字会自动转换。

注意:**= 前后不要加空格,平时写代码加空格习惯了,这里要注意了!!!**

1
2
3
name1=tonngw # 不加引号
name2='tonngw' # 加单引号
name3="tonngw" # 加双引号

只读变量

使用 readonly 或者 declare 将变量变为只读

1
2
3
4
5
name=tonngw
readonly name
#declare -r name # 两种写法

name=abc # 运行报错,不能修改只读变量

删除变量

unset 删除变量

1
2
3
name=tonngw
unset name
echo $name # 输出空串

变量类型

  1. 自定义变量(局部变量),子进程(比如新开一个 bash,退出 exit 或 Ctrl + d)不能访问
  2. 环境变量(全局变量),所有进程进行都可以访问

自定义变量改成环境变量

1
2
3
$ name=tonngw # 定义变量
$ export name # 导出为全局变量(法 1)
$ declare -x name # 声明为全局变量(法 2),- 号添加

环境变量改为自定义变量

1
2
export name=tonngw # 定义环境变量
declare +x name # 改为自定义变量,+ 号取消

字符串

字符串可以用 ''"" 或者不用引号定义

单引号和双引号的区别:

  • 单引号里的内容会原样输出
  • 双引号里的内容可以转义、取变量、执行
1
2
3
name=yxc  # 不用引号
echo 'hello, $name \"hh\"' # 单引号字符串,输出 hello, $name \"hh\"
echo "hello, $name \"hh\"" # 双引号字符串,输出 hello, yxc "hh"

获取字符长度

1
2
name=tonngw
echo ${#name} # 长度为 3

提取字串

1
2
name="hello, world"
echo ${name:0:5} # 提取从 0 开始的 5 个字符,和 c++ substr(pos, len)一样

默认变量

文件参数变量

在执行 shell 脚本的时候可以传递参数,$1 是第一个参数,$2 是第二个参数,以此类推,$0 是当前文件的绝对路径名

创建脚本文件 test.sh

1
2
3
4
5
6
7
#! /bin/bash

echo "文件名:"$0
echo "第一个参数:"$1
echo "第二个参数:"$2
echo "第三个参数:"$3
echo "第四个参数:"$4

执行脚本

1
2
3
4
5
6
7
acs@9e0ebfcd82d7:~$ chmod +x test.sh 
acs@9e0ebfcd82d7:~$ ./test.sh 1 2 3 4
文件名:./test.sh
第一个参数:1
第二个参数:2
第三个参数:3
第四个参数:4

其他参数相关变量

参数 说明
$# 文件传入参数的个数
$* 由所有参数构成的用空格隔开的字符串,如上例中值为”$1 $2 $3 $4”
$@ 每个参数分别用双引号括起来的字符串,如上例中值为”$1” “$2” “$3” “$4”
$$ 获取脚本进程 ID
$? 获取上一条命令的退出状态,0 正常,其他错误
$(command) 返回 command 的输出,可嵌套,比如:$(ls)
command 返回 command 的输出,不可嵌套

数组

数组可以存放多种不同类型的值(但本质上都是字符串),只支持一维数组,初始化时不需要指明大小,下标从 0 开始,可以跳着用下标,但实际占用的空间是有值的下标。

定义

法 1:小括号包围,内部元素用 空格 隔开

1
array=(1 abc 'def' "ghi")

法 2:用下标给数组赋值

1
2
3
4
5
array[0]=1
array[1]=abc
array[2]='def'
array[3]="ghi"
array[1000]=jkl # 可以选任意下标进行赋值,不连续也行

获取数组某个元素的值

通过下标获取 ${array[index]}

1
2
3
4
5
array=(1 abc 'def' "ghi")
echo ${array[0]}
echo ${array[1]}
echo ${array[2]}
echo ${array[3]}

读取整个数组

1
2
${array[@]} # 法 1
${array[*]} # 法 2

例如:

1
2
3
array=(1 abc 'def' "ghi")
echo ${array[@]} # 法 1
echo ${array[*]} # 法 2

获取数组长度

类似于字符串获取长度,前面加一个 #

1
2
${#array[@]} # 法 1
${#array[*]} # 法 2

expr 命令

expr 表达式 命令用于求表达式的值

使用说明:

  • 用空格分隔每一项
  • 特殊字符前用反斜杠 \ 转义
  • 对 包含空格和其他特殊字符 的字符串用引号括起来
  • expr 会在 stdout 输出结果,如果是逻辑关系表达式,括号为真输出 1,否则输出 0(对比 ? 表达式)
  • expr 的 exit code(返回值):如果是逻辑关系表达式,结果为真返回值为 0,否则为 1(对比 return 0 正常退出,其他非正常退出)

字符串表达式

  • length 字符串,返回字符串的长度
  • index 字符串 CHARSET,返回在 CHARSET(字符集 abc)任意单个字符在字符串中最前面的字符位置,下标从 1 开始。如果字符串不存在 CHARSET 中的字符,则返回 0。
  • substr 字符串 位置 长度,返回字符串从指定位置开始固定长度的子串,如果位置或长度为负数、0 或非数值则返回空串。

示例:

1
2
3
4
5
str="Hello World!"

echo `expr length "$str"` # ``不是单引号,表示执行该命令,输出12
echo `expr index "$str" aWd` # 输出7,下标从1开始
echo `expr substr "$str" 2 3` # 输出 ell

整数表达式

expr 支持普通的算术操作,算术表达式优先级低于字符串表达式,高于逻辑关系表达式。

  • + -:加减运算,两端参数会转换为整数,如果转换失败则报错。
  • * / %:乘除、取余运算,两端参数会转换为整数,如果转换失败则报错。
  • ():可以改变优先级,但需要用反斜杠转义

示例

1
2
3
4
5
6
7
8
9
a=3
b=4

echo `expr $a + $b` # 输出7
echo `expr $a - $b` # 输出-1
echo `expr $a \* $b` # 输出12,*需要转义
echo `expr $a / $b` # 输出0,整除
echo `expr $a % $b` # 输出3
echo `expr \( $a + 1 \) \* \( $b + 1 \)` # 输出20,值为(a + 1) * (b + 1)

逻辑关系表达式

是短路运算符

  • | 类似于 C++ 的 || 但返回值不一样

    如果参数 1 非空且非 0,则返回参数 1 的值,不会计算参数 2 的值。(短路)

    否则如果返回参数 2 的值非空且非 0,则返回参数 2 的值,否则返回 0

  • & 类似于 C++ 的 && 但返回值不一样

    如果两个参数都非空且非 0,则返回参数 1 的值,不会计算参数 2 的值(短路),否则返回 0

  • <、<=、=、==、!=、>=、>

    比较两端的参数,如果为true,则返回1,否则返回0。”==”是”=”的同义词。”expr”首先尝试将两端参数转换为整数,并做算术比较,如果转换失败,则按字符集排序规则做字符比较。

  • ():可以改变优先级,但需要用反斜杠转义

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
a=3
b=4

echo `expr $a \> $b` # 输出0,>需要转义
echo `expr $a '<' $b` # 输出1,也可以将特殊字符用引号引起来
echo `expr $a '>=' $b` # 输出0
echo `expr $a \<\= $b` # 输出1

c=0
d=5

echo `expr $c \& $d` # 输出0
echo `expr $a \& $b` # 输出3
echo `expr $c \| $d` # 输出5
echo `expr $a \| $b` # 输出3

read 命令

read 命令用于标准输入中读取单行数据。当读到文件结束符时,exit code 为 1,否则为 0。(类似于 C++ cin)

参数:

  • p:后面可以接提示信息
  • -t:后面跟秒数 xs,指定接受用户输入的等待时间,超时自动结束

示例

1
2
3
4
5
6
7
8
acs@9e0ebfcd82d7:~$ read name  # 读入name的值
acwing yxc # 标准输入
acs@9e0ebfcd82d7:~$ echo $name # 输出name的值
acwing yxc #标准输出
acs@9e0ebfcd82d7:~$ read -p "Please input your name: " -t 30 name # 读入name的值,等待时间30秒
Please input your name: acwing yxc # 标准输入
acs@9e0ebfcd82d7:~$ echo $name # 输出name的值
acwing yxc # 标准输出

echo 命令

echo 用于输出字符串

echo STRING

显示普通字符串

1
2
echo "Hello AC Terminal"
echo Hello AC Terminal # 引号可以省略

显示转义字符

1
2
echo "\"Hello AC Terminal\""  # 注意只能使用双引号,如果使用单引号,则不转义
echo \"Hello AC Terminal\" # 也可以省略双引号

显示变量

1
2
name=tonngw
echo "My name is $name" # 输出 My name is tonngw

显示换行

默认 echo 输出会换行,这里指的是两个换行

1
2
echo -e "Hi\n" # -e 开启转义,输出换行
echo "acwing"

显示不换行

1
2
echo -e "Hi \c" # -e 开启转义,\c 不换行,要放在双引号里面哦~
echo "acwing"

显示结果定向至文件

1
echo "Hello World" > output.txt  # 将内容以覆盖的方式输出到output.txt中

原样输出字符串,不进行转义或取变量(单引号)

1
2
3
4
5
name=acwing
echo '$name\"'

# output
$name\"

显示命令的执行结果

1
2
3
echo `date`
# output
Wed Mar 30 21:19:32 CST 2022

printf 命令

printf 命令用于格式化输出,类似于 C/C++ 中的 printf 函数。

默认不会在字符串末尾添加换行符,区别于 echo 默认输出会换行

printf format-string [arguments...]

示例:

1
2
3
4
5
6
7
8
9
10
printf "%10d.\n" 123  # 占10位,右对齐
printf "%-10.2f.\n" 123.123321 # 占10位,保留2位小数,左对齐
printf "My name is %s\n" "yxc" # 格式化输出字符串
printf "%d * %d = %d\n" 2 3 `expr 2 \* 3` # 表达式的值作为参数

# output
123.
123.12 .
My name is yxc
2 * 3 = 6

test 命令和判断符号 []

test 和 [] 用法基本一样

逻辑运算符 && 和 ||

之前的 expr 里的逻辑关系运算符是 & 和 |,不一样哦

  • && 表示与,|| 表示或

  • 二者具有短路原则:(和 C++ 完全一样)
    expr1 && expr2:当 expr1 为假时,直接忽略 expr2【通常会使用 && 链接执行多条命令
    expr1 || expr2:当 expr1 为真时,直接忽略 expr2

  • 表达式的 exit code 为0,表示真;为非零,表示假。(与C/C++中的定义相反)

test 命令

在命令行中输入 man test,可以查看 test 命令的用法。

test 命令用于判断文件类型,以及对变量做比较。

test 命令用 exit code 返回结果,而不是使用 stdout。0表示真,非0表示假。

例如:

1
2
test 2 -lt 3  # 为真,返回值为0
echo $? # 输出上个命令的返回值,输出0
1
2
3
4
5
6
acs@a7e3435d46dd:~$ ls                                                                                  
homework test.cpp test.sh tmp
acs@a7e3435d46dd:~$ test -e test.sh && echo "exist" || echo "not exist"
exist # 存在
acs@a7e3435d46dd:~$ test -e test2.sh && echo "exist" || echo "not exist"
not exist # 不存在
文件类型判断

test -e filename 判断文件是否存在

  • -e:判断文件是否存在
  • -f:判断是否为文件
  • -d:判断是否为目录
文件权限判断

test -r filename 判断文件是否可读

  • -r:是否可读
  • -w:是否可写
  • -x:是否可执行
  • -s:是否为非空文件
整数比较

test $a -eq $b a 是否等于 b

  • -eq:是否等于
  • -ne:是否不等于
  • -gt:是否大于
  • -lt:是否小于
  • -ge:是否大于等于
  • -le:是否小于等于
字符串比较
  • test -z STRING:判断 STRING 是否为空,如果为空,则返回 true
  • test -n STRING:判断 STRING 是否非空,如果非空,则返回 true(-n 可以省略)
  • test str1 == str2:判断 str1 是否等于 str2
  • test str1 != str2:判断 str1 是否不等于 str2
多重条件判定

test -r filename -a -x filename

  • -a:and,两条件是否同时成立
  • -o:or,两条件是否至少一个成立
  • !:取反,如 test ! -x file,当 file 不可执行时,返回 true

判断符号 []

[]test 的用法几乎一致,常用于 if 语句中作为条件,另外 [[]] 是 [] 的加强版,支持的特性更多。

1
2
[ 2 -lt 3 ]  # 为真,返回值为0
echo $? # 输出上个命令的返回值,输出0
1
2
3
4
5
6
acs@9e0ebfcd82d7:~$ ls  # 列出当前目录下的所有文件
homework output.txt test.sh tmp
acs@9e0ebfcd82d7:~$ [ -e test.sh ] && echo "exist" || echo "Not exist"
exist # test.sh 文件存在
acs@9e0ebfcd82d7:~$ [ -e test2.sh ] && echo "exist" || echo "Not exist"
Not exist # testh2.sh 文件不存在

注意:

  • [] 内的每一项需要用空格隔开
  • [] 内的变量,最好用 "" 括起来
  • [] 内的常数,最好用 "" 或者 '' 括起来

例如:

1
2
3
name="acwing yxc"
[ $name == "acwing yxc" ] # 错误,等价于 [ acwing yxc == "acwing yxc" ],参数太多
[ "$name" == "acwing yxc" ] # 正确

判断语句

if … then 形式

类似于 C/C++ 中的 if-else 语句。

单层 if

1
2
3
4
5
6
if condition
then
语句1
语句2
...
fi

示例

1
2
3
4
5
6
7
8
9
10
a=3
b=4

if [ "$a" -lt "$b" ] && [ "$a" -gt 2 ]
then
echo ${a}在范围内
fi

# output
3在范围内

单层 if-else

1
2
3
4
5
6
7
8
9
10
if condition
then
语句1
语句2
...
else
语句1
语句2
...
fi

多层 if-elif-elif-else

和 Python 类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if condition
then
语句1
语句2
...
elif condition
then
语句1
语句2
...
elif condition
then
语句1
语句2
else
语句1
语句2
...
fi

case … esac 形式

类似于 C/C++ 中的 switch 语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
case $变量名称 in
值1)
语句1
语句2
...
;; # 类似于C/C++中的break
值2)
语句1
语句2
...
;;
*) # 类似于C/C++中的default
语句1
语句2
...
;;
esac

循环语句

do 和 done 可以看成是 { 和 }

for…in…do…done

类似于 C++ 的增强 for 循环

1
2
3
4
5
6
for var in val1 val2 val3
do
语句1
语句2
...
done

示例 1,输出a 2 cc,每个元素一行:

1
2
3
4
for i in a 2 cc
do
echo $i
done

示例 2,输出当前路径下所有文件名,每个文件名一行

1
2
3
4
for file in `ls`
do
echo $file
done

示例 3,输出 1-10

1
2
3
4
for i in $(seq 1 10)
do
echo $i
done

示例 4,使用{1..10} 或者 {a..z}

1
2
3
4
for i in {a..z}
do
echo $i
done

for ((…;…;…)) do…done

类似于 C++ 的普通 for 循环

1
2
3
4
5
for ((expression; condition; expression))
do
语句1
语句2
done

示例,输出1-10,每个数占一行:

1
2
3
4
for ((i=1; i<=10; i++))
do
echo $i
done

while…do…done 循环

类似于 C++ 的 while 循环

1
2
3
4
5
6
while condition
do
语句1
语句2
...
done

示例,**文件结束符为 Ctrl+d**,输入文件结束符后 read 指令返回 false。

1
2
3
4
while read name
do
echo $name
done

util…dp…done 循环

当 until 条件为真时结束,而 while 是条件为真时进入循环,和 while 相反

1
2
3
4
5
6
until condition
do
语句1
语句2
...
done

示例,当用户输入 yes 或者 YES 时结束,否则一直等待读入。

1
2
3
4
until [ "${word}" == "yes" ] || [ "${word}" == "YES" ]
do
read -p "Please input yes/YES to stop this program: " word
done

break 命令

跳出每一层循环,和 C++ 不同的是:break 不能跳出 case 语句,而是用;; 跳出 case。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
while read name
do
for ((i=1;i<=10;i++))
do
case $i in
8)
break
;;
*)
echo $i
;;
esac
done
done

该示例每读入非EOF的字符串,会输出一遍1-7。
该程序可以输入 Ctrl+d 文件结束符来结束,也可以直接用 Ctrl+c 杀掉该进程。

continue 命令

跳出当前循环

示例:

1
2
3
4
5
6
7
8
for ((i=1;i<=10;i++))
do
if [ `expr $i % 2` -eq 0 ]
then
continue
fi
echo $i
done

该程序输出1-10中的所有奇数。

死循环的处理方式

Ctrl + c 可以结束当前程序运行

否则可以通过进程号关闭进程

  1. 使用 top 命令找到要结束的进程 PID,按 Ctrl + F 按内容从大到小排序(方便观察进程),或者 ps aux | grep 要搜索的进程

    top 命令中的快捷键

  2. 输入 kill -9 PID 杀掉进程

函数

bash 中的函数类似于 C/C++ 中的函数,但 return 的返回值与 C/C++ 不同,返回的是exit code,取值为0-255,0表示正常结束。

如果想获取函数的输出结果,可以通过 echo 输出到 stdout 中,然后通过 $(func_name) 来获取 stdout 中的值。

1
2
3
4
5
[function] func_name() {  # function关键字可以省略
语句1
语句2
...
}

不获取 return 值和 stdout 值

1
2
3
4
5
6
7
8
9
func() {
name=tonngw
echo "Hello $name"
}

func

# output
Hello tonngw

获取 return 值和 stdout 值

不写 return 时,默认 return 0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func() {
name=tonngw
echo "Hello $name"

return 123
}

output=$(func)
ret=$?

echo "output = $output"
echo "return = $ret"

# output
output = Hello tonngw
return = 123

函数的输入参数

在函数内,$1表示第一个输入参数,$2 表示第二个输入参数,依此类推。

注意:函数内的 $0 仍然是文件名,而不是函数名。

示例:递归求和 0-10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func() {  # 递归计算 $1 + ($1 - 1) + ($1 - 2) + ... + 0
word=""
while [ "${word}" != 'y' ] && [ "${word}" != 'n' ]
do
read -p "要进入func($1)函数吗?请输入y/n:" word
done

if [ "$word" == 'n' ]
then
echo 0
return 0
fi

if [ $1 -le 0 ]
then
echo 0
return 0
fi

sum=$(func $(expr $1 - 1))
echo $(expr $sum + $1)
}

echo $(func 10)

# output
55

函数内的局部变量

可以在函数内定义局部变量,作用范围仅在当前函数内。

可以在递归函数中定义局部变量。

local 变量名=变量值

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
#! /bin/bash

func() {
local name=tonngw # 局部变量
echo $name
}
func

echo $name

# output
tonngw # 局部变量
# 全局变量为空串

exit 命令

exit 命令用来退出当前 shell 进程,并返回一个退出状态,使用 $? 可以接收这个退出状态。

exit 命令可以接收一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。

exit 退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。

文件重定向

每个进程默认打开3个文件描述符:

  • stdin 标准输入,从命令行读取数据,文件描述符为 0
  • stdout 标准输出,向命令行输出数据,文件描述符为 1
  • stderr 标准错误输出,向命令行输出数据,文件描述符为2

重定向命令

  • command > file:将 stdout 重定向到 file 中,即命令的结果输出到 file 中,但每次执行会覆盖 file 中的内容
  • command < file:将 stdin 重定向到 file 中,命令的输入来自 file
  • command >> file:将 stdout 重定向到 file 中,即命令的结果输出到 file 中,但每次执行追加 file 中的内容
  • command n> file:将文件描述符 n(0 1 2) 对应的文件重定向到 file
  • command n>> file:将文件描述符 n(0 1 2) 对应的文件以追加的方式重定向到 file

示例:组合拳,test.sh 从 input.txt 中获取输入,输出到 output.txt

./test.sh < input.txt > output.txt,或者 ./test.sh > output.txt < input.txt

引入外部脚本

类似于 C/C++ 中的 include 操作,bash 也可以引入其他脚本文件

1
2
3
. filename  # 注意点和文件名之间有一个空格
或者
source filename

作业代码

评测作业的时候执行 ls -a 保证当前目录下只有脚本文件。

homework_0

进入 cd homework_0

创建 helper.sh 文件 touch helper.sh

编辑 helper.sh 脚本文件

最后把 helper.sh 的内容复制出来 :

  • 先退出 tmux,Ctrl + a 之后 d
  • 然后 cat helper.sh
  • 按住 Shift 选中文件内容,Ctrl + Insert 复制,Shift + Insert 在 Linux/Windows 下粘贴,Windows 下也可以用 Ctrl + v 粘贴。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#! /bin/bash

# [0]
homework 1 create 0

dir=/home/acs/homework/lesson_1
dir0=${dir}/homework_0

for i in dir_a dir_b dir_c
do
mkdir ${dir0}/$i
done

# [1]
homework 1 create 1

dir1=${dir}/homework_1

for i in a.txt b.txt c.txt
do
cp ${dir1}/${i} ${dir1}/${i}.bak
done

# [2]
homework 1 create 2

dir2=${dir}/homework_2

for i in a b c
do
mv ${dir2}/${i}.txt ${dir2}/${i}_new.txt
done

#[3]
homework 1 create 3

dir3=${dir}/homework_3

mv ${dir3}/dir_a/* ${dir3}/dir_b

# [4]
homework 1 create 4

dir4=${dir}/homework_4

for i in a.txt b.txt c.txt
do
rm ${dir4}/${i}
done

# [5]
homework 1 create 5

dir5=${dir}/homework_5

for i in dir_a dir_b dir_c
do
rm -r ${dir5}/${i}
done

# [6]
homework 1 create 6

dir6=${dir}/homework_6

mkdir ${dir6}/dir_a
mv ${dir6}/task.txt ${dir6}/dir_a/done.txt

# [7]
homework 1 create 7

dir7=${dir}/homework_7

mkdir ${dir7}/dir_0 ${dir7}/dir_1 ${dir7}/dir_2

for i in a b c
do
for j in 0 1 2
do
cp ${dir7}/${i}.txt ${dir7}/dir_${j}/${i}${j}.txt
done

done

# [8]
homework 1 create 8

dir8=${dir}/homework_8

rm ${dir8}/dir_a/a.txt

mv ${dir8}/dir_b/b.txt ${dir8}/dir_b/b_new.txt

cp ${dir8}/dir_c/c.txt ${dir8}/dir_c/c.txt.bak


# [9]
homework 1 create 9

dir9=${dir}/homework_9

rm ${dir9}/*.txt

# homework 1 test

homework_1

法 1:[ ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#! /bin/bash

if [ $# -ne 1 ]
then
echo "arguments not valid"
exit 1
fi

if [ ! -e "$1" ]
then
echo "not exist"
exit 2
fi

if [ -f "$1" ]
then
echo "regular file"
fi

if [ -d "$1" ]
then
echo "directory"
fi

if [ -r "$1" ]
then
echo "readable"
fi

if [ -w "$1" ]
then
echo "writable"
fi

if [ -x "$1" ]
then
echo "executable"
fi

法 2:test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#! /bin/bash

filename=$1

if test $# -ne 1
then
echo "arguments not valid"
exit 1
fi

if test ! -e "${filename}"
then
echo "not exist"
exit 2
fi

if test -f "${filename}"
then
echo "regular file"
fi

if test -d "${filename}"
then
echo "directory"
fi

if test -r "${filename}"
then
echo "readable"
fi

if test -w "${filename}"
then
echo "writable"
fi

if test -x "${filename}"
then
echo "executable"
fi

homework_2

法 1:变量实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#! /bin/bash

read n
func() {
a=1
b=1
for ((i=2;i<=n;i++))
do
c=$(expr $a + $b)
a=${b}
b=${c}
#echo a=${a}
#echo b=${b}
#echo c=${c}
#echo ""
done
echo ${b}
}

echo $(func)

法 2:数组实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#! /bin/bash

read n

a[0]=1
a[1]=1

for ((i = 2; i <= n; i ++ ))
do
x=$(expr $i - 1)
y=$(expr $i - 2)
a[$i]=$(expr ${a[$x]} + ${a[$y]}) # 注意这里 a[i] 外面也要加 $ 取它的值
done

echo ${a[$n]}

homework_3

main.sh 对照 C++ 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#! /bin/bash

read n
read m

for ((i = 1; i <= n; i ++ ))
do
st[$i]=0 # 0 表示false, 1 表示 true
done

# dfs 返回值 0 表示 true 1 表示 false
dfs() {
if [ $1 -eq $n ]
then
m=`expr $m - 1`
if [ $m -eq 0 ]
then
echo ${path[@]}
return 0
fi

return 1
fi

# 递归这里需要用局部变量
local j=0
for ((j = 1; j <= n; j ++ ))
do
if [ ${st[$j]} -eq 0 ]
then
path[$1]=$j
st[$j]=1

if dfs `expr $1 + 1`
then
return 0
fi

st[$j]=0
fi
done
return 1
}

dfs 0

C++ 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110;

int n, m;
int path[N];
bool st[N];

bool dfs(int u) {
if (u == n) {
m -- ;
if (!m) {
for (int i = 0; i < n; i ++ ) cout << path[i] << " ";
cout << endl;
return true;
}
return false;
}

for (int i = 1; i <= n; i ++ ) {
if (!st[i]) {
st[i] = true;
path[u] = i;
if (dfs(u + 1)) return true;
st[i] =false;
}
}

return false;
}

int main()
{
cin >> n>> m;

dfs(0);

return 0;
}

homework_4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#! /bin/bash

input_file=$1
output_file=$2

read n < ${input_file}

sum=0

for ((i = 1; i <= n; i ++ ))
do
sum=`expr $sum + $i \* $i`
done

echo ${sum} > ${output_file}

image-20220331194139317.png

Author: tonngw
Link: https://tonngw.com/2022/04/01/AcWing/第 3 讲 Shell 语法/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.