本文是介绍shell 语言的学习笔记。
shell简介
Shell既是一种命令语言,又是一种程序设计语言。
作为命令语言,它可以交互式地解释和执行用户输入的命令;而作为程序设计语言,它可以定义各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。
新建一个文本,扩展名是sh(其实扩展名并不影响脚本执行),但是为了分辨
1
2
|
#! /bin/bash
echo "Hello world!"
|
“#!” 是一个约定的标记,告诉系统这个脚本使用什么样的解释器来执行。以下这种执行方式,即使在文件的第一行指定了解释器,那也是不起作用的。
只有使用以下方式的时候,才是使用的文件中的shell 脚本。
shell变量
变量名的花括号是可选的,是为了帮助识别器识别边来那个的边界。如果没有花括号,那么很有可能把 skillScript当做了一个变量进行处理。
1
2
3
4
|
for skill in Ada Coffe Action Java
do
echo "I am good at ${skill}Script"
done
|
使用的时候加上美元符号,但是重新定义变量时候,是不需要加的。
1
2
3
4
|
myUrl="http://justcode.ikeepstudying.com"
echo ${myUrl}
myUrl="http://justcode.ikeepstudying.com"
echo ${myUrl}
|
只读模式
1
2
3
4
|
#!/bin/bash
myUrl="http://justcode.ikeepstudying.com"
readonly myUrl
myUrl="http://justcode.ikeepstudying.com"
|
删除一个变量
1
2
3
4
|
#!/bin/sh
myUrl="http://justcode.ikeepstudying.com"
unset myUrl
echo $myUrl
|
变量类型
-
局部变量
局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
-
环境变量
所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。
-
shell特殊变量
shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行。下面就介绍下shell中的特殊变量。
\begin{table}[]
\begin{tabular}{ll}
名称 & 含义 \\
$0 & 当前脚本的文件名 \\
$# & 传递给脚本或函数的参数个数。 \\
$* & 传递给脚本或函数的所有参数。 \\
$@ & 传递给脚本或函数的所有参数。被双引号(” “)包含时,与 $* 稍有不同,下面将会单独讲到。 \\
$? & 上个命令的退出状态,或函数的返回值。 \\
$$ & 当前进程的ID。对于 Shell 脚本,就是这些脚本所在的进程ID \\
$n & 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2
\end{tabular}
\end{table}
$* 和 $@ 的区别
获取命令行参数
运行脚本时传递给脚本的参数称为命令行参数。命令行参数用 $n 表示,例如,$1 表示第一个参数,$2 表示第二个参数,依次类推。
1
2
3
4
5
6
7
|
#!/bin/bash
echo "文件名: $0"
echo "第一个参数 : $1"
echo "第二个参数 : $2"
echo "所有参数: $@"
echo "所有参数: $*"
echo "参数个数: $#"
|
shell 注释
Shell中的注释以“#”号开头,所有以“#”号开头的代码都会被解释器所忽略。shell 中没有多行注释,只有单行注释。(尴尬的事情)
(在shell 脚本中加上 Author 和 Date 是很好的习惯,尤其是在团队多人协作中)
1
2
3
4
5
6
|
#!/bin/bash
# Author : justcode.ikeepstudying.com
# Date : 2016-05-15
echo "What is your name?"
read PERSON
echo "Hello, $PERSON"
|
shell字符串
字符串在编程中是最常用最有用的数据类型。shell 的字符串可以用引号包起来,也可以不用引号。用引号可以用单引号也可以使用双引号。
加单引号
- Shell单引号里的任何字符都会被原样输出,单引号字符串中的变量无效;
- Shell单引号字串中不能出现单引号(对单引号使用转义符也不行)。
1
|
str='justcode.ikeepstudying.com'
|
加双引号
- Shell双引号里可以有变量
- Shell双引号里可以出现转义字符
1
2
|
myweb='justcode.ikeepstudying.com'
str="Hello, you are browsing \"$myweb\"! \n"
|
所以,建议大家在使用Shell时,对字符串要加上引号,而且最好加双引号。
shell 中对于字符串的操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# 获取字符串的长度
[justcode@ikeepstudying ~]$ test='I love china'
[justcode@ikeepstudying ~]$ echo ${#test}
#12
# 截取字符串
[justcode@ikeepstudying ~]$ test='I love china'
[justcode@ikeepstudying ~]$ echo ${test:5}
#e china
[justcode@ikeepstudying ~]$ echo ${test:5:10}
#e china
# 字符串的替换
# ${变量/查找/替换值} 一个“/”表示替换第一个,”//”表示替换所有,当查找中出现了:”/”请加转义符”\/”表示。
|
shell数组
在 shell中,使用括号来表示数组,数组元素之间使用“空格”分隔开。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# 第一种写法
array_name=(value0 value1 value2 value3)
# 第二种写法
array_name=(
value0
value1
value2
value3
)
# 第三种写法
array_name[0]=value0
array_name[1]=value1
array_name[2]=value2
|
shell 数组的读取,使用@ 或 * 可以获取数组中的所有元素。
1
2
3
4
5
6
7
8
9
|
#!/bin/sh
#justcode@ikeepstudying
NAME[0]="Zara"
NAME[1]="Qadir"
NAME[2]="Mahnaz"
NAME[3]="Ayan"
NAME[4]="Daisy"
echo "First Method: ${NAME[*]}"
echo "Second Method: ${NAME[@]}"
|
运算符
数学表达式
\begin{table}[]
\begin{tabular}{lll}
运算符 & 说明 & 举例 \\
- & 加法 &
expr $x + $y
结果为 30。 \\
- & 减法 &
expr $x - $y
结果为 -10。 \\
- & 乘法 &
expr $x * $y
结果为 200。 \\
/ & 除法 & expr $y / $x
结果为 2。 \\
% & 取余 & expr $y % $x
结果为 0。 \\
= & 赋值 & x=\$y
将把变量 y 的值赋给 x。 \\
== & 相等。用于比较两个数字,相同则返回 true。 & {[} $x == $y {]}
返回 false。 \\
!= & 不相等。用于比较两个数字,不相同则返回 true。 & {[} $x != $y {]}
返回 true。
\end{tabular}
\end{table}
数学表达式例子
{% fold 开/合 %}
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
|
x=10
y=20
echo "x=${x}, y=${y}"
val=`expr ${x} + ${y}`
echo "${x} + ${y} = $val"
val=`expr ${x} - ${y}`
echo "${x} - ${y} = $val"
val=`expr ${x} \* ${y}`
echo "${x} * ${y} = $val"
val=`expr ${y} / ${x}`
echo "${y} / ${x} = $val"
val=`expr ${y} % ${x}`
echo "${y} % ${x} = $val"
if [[ ${x} == ${y} ]]
then
echo "${x} = ${y}"
fi
if [[ ${x} != ${y} ]]
then
echo "${x} != ${y}"
fi
# Execute: ./operator-demo.sh
# Output:
# x=10, y=20
# 10 + 20 = 30
# 10 - 20 = -10
# 10 * 20 = 200
# 20 / 10 = 2
# 20 % 10 = 0
# 10 != 20
|
{% endfold %}
条件表达式
条件表达式要放在方括号之间,并且要有空格,例如: [$x==$y]
是错误的,必须写成 [ $x == $y ]
。
关系表达式
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
\begin{table}[]
\begin{tabular}{lll}
运算符 & 说明 & 举例 \
-eq & 检测两个数是否相等,相等返回 true。 & {[} $a -eq $b {]}返回 false。 \
-ne & 检测两个数是否相等,不相等返回 true。 & {[} $a -ne $b {]} 返回 true。 \
-gt & 检测左边的数是否大于右边的,如果是,则返回 true。 & {[} $a -gt $b {]} 返回 false。 \
-lt & 检测左边的数是否小于右边的,如果是,则返回 true。 & {[} $a -lt $b {]} 返回 true。 \
-ge & 检测左边的数是否大于等于右边的,如果是,则返回 true。 & {[} $a -ge $b {]} 返回 false。 \
-le & 检测左边的数是否小于等于右边的,如果是,则返回 true。 & {[} $a -le $b {]}返回 true。
\end{tabular}
\end{table}
bool 表达式例子
{% fold 开/合 %}
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
|
x=10
y=20
echo "x=${x}, y=${y}"
if [[ ${x} != ${y} ]]; then
echo "${x} != ${y} : x 不等于 y"
else
echo "${x} != ${y}: x 等于 y"
fi
if [[ ${x} -lt 100 && ${y} -gt 15 ]]; then
echo "${x} 小于 100 且 ${y} 大于 15 : 返回 true"
else
echo "${x} 小于 100 且 ${y} 大于 15 : 返回 false"
fi
if [[ ${x} -lt 100 || ${y} -gt 100 ]]; then
echo "${x} 小于 100 或 ${y} 大于 100 : 返回 true"
else
echo "${x} 小于 100 或 ${y} 大于 100 : 返回 false"
fi
if [[ ${x} -lt 5 || ${y} -gt 100 ]]; then
echo "${x} 小于 5 或 ${y} 大于 100 : 返回 true"
else
echo "${x} 小于 5 或 ${y} 大于 100 : 返回 false"
fi
# Execute: ./operator-demo3.sh
# Output:
# x=10, y=20
# 10 != 20 : x 不等于 y
# 10 小于 100 且 20 大于 15 : 返回 true
# 10 小于 100 或 20 大于 100 : 返回 true
# 10 小于 5 或 20 大于 100 : 返回 false
|
{% endfold %}
逻辑表达式例子:
{% fold 开/合 %}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
x=10
y=20
echo "x=${x}, y=${y}"
if [[ ${x} -lt 100 && ${y} -gt 100 ]]
then
echo "${x} -lt 100 && ${y} -gt 100 返回 true"
else
echo "${x} -lt 100 && ${y} -gt 100 返回 false"
fi
if [[ ${x} -lt 100 || ${y} -gt 100 ]]
then
echo "${x} -lt 100 || ${y} -gt 100 返回 true"
else
echo "${x} -lt 100 || ${y} -gt 100 返回 false"
fi
# Execute: ./operator-demo4.sh
# Output:
# x=10, y=20
# 10 -lt 100 && 20 -gt 100 返回 false
# 10 -lt 100 || 20 -gt 100 返回 true
|
{% endfold %}
shell输出
(1)使用echo
该命令打印出指定的字符串
输出重定向:Shell可以使用右尖括号(“>”)和两个右尖括号(“»”)来表示输出的重定向
1
2
3
4
|
echo "It is a test" > myfile
#将字符串重定向入myfile这个文件中,myfile中原有内容会被清除
echo "It is a test" >> myfile
#将字符串重定向入myfile这个文件中,myfile中原有内容不会被清除,新内容会追加到文件结尾处
|
保持原样输出
(2)printf
该命令用于格式化输出, 是echo命令的增强版。但需要注意的是,该命令不能自动换行,需要注意一下。
1
2
3
4
5
6
7
|
# printf format-string [arguments...]
printf "%d %s\n" 1 "abc"
# 如果只是指定了一个参数,那么多出来的参数仍然会按照原来的格式输出
printf "%s\n" abc def
# 这个是比较规范的一个例子
printf "The first program always prints'%s,%d\n'" Hello Shell
|
最常用的格式指示符有两个,%s用于字符串,而%d用于十进制整数。
if else条件语句
shell 中有三种if else格式:
- if … fi 格式
- if … else … fi 格式
- if … elif … else … fi 格式
从下面的例子可以发现if 之后是判断,然后then 之后才是执行的相关语句
第一个例子
1
2
3
4
5
6
7
8
9
10
11
12
|
#!/bin/sh
a=400
b=800
if [ $a == $b ]
then
echo "a is equal to b"
fi
if [ $a != $b ]
then
echo "a is not equal to b"
fi
|
第二个例子
1
2
3
4
5
6
7
8
9
|
#!/bin/sh
a=400
b=800
if [ $a == $b ]
then
echo "a is equal to b"
else
echo "a is not equal to b"
fi
|
第三个例子,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#!/bin/sh
a=400
b=800
if [ $a == $b ]
then
echo "a is equal to b"
elif [ $a -gt $b ]
then
echo "a is greater than b"
elif [ $a -lt $b ]
then
echo "a is less than b"
else
echo "None of the condition met"
fi
|
for循环
列表是一组值(数字,字符串)组成的序列,每个值都是通过空格分隔。每循环一次,就将列表中的值依次放入到指定的变量中,然后重复执行 do 和done之间,直到所有元素取尽为止。
1
2
3
4
|
for loop in one two tree four
do
echo "I am : $loop"
done
|
下面的代码在文件处理中更加常见。
1
2
3
4
|
for file in ./*
do
echo $file
done
|
while 循环
这个例子中使用 read 标准输入读取数据,放到text 中,如果读到的数据非空,那么一直循环。最后输入重定向,将文本 /home/infile 作为文本的输入。
1
2
3
4
5
|
#!/bin/bash
while read text
do
echo ${text}
done < /home/infile
|
until 循环
shell 中until 和while 循环差不多,区别在于while 条件测试是检测真值,until循环则是检测假值。也就是说在while 循环中,如果测试条件的结果为真,那么就进入循环;在until 循环中,如果测试条件真,那么就跳出循环。
分支语句 case(select)
这个是该语法结构。(感觉shell 中没有必要写这么复杂的语句)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
case 值 in
模式1)
command1
command2
command3
;;
模式2)
command1
command2
command3
;;
*)
command1
command2
command3
;;
esac
|
shell函数
shell 是脚本类语言,在执行的时候是逐行执行,所以shell函数必须先定义后使用。shell函数的定义格式如下:
1
2
3
4
5
6
7
|
[ function ] funname [()]
{
command;
[return int;]
}
funname
|
说明,function 关键字是可选项,可加可不加。大括号内是函数体,最后是返回值(可加可不加)。通过函数名调用函数。
下面的例子中,echo "Return :"$total,$?
有点意思。
1
2
3
4
5
6
7
8
9
10
11
12
|
#!/bin/sh
#Author:Linux大学
#url:http://www.linuxdaxue.com
#date:2016-06-01
function fSum()
{
echo "入参为:"$1,$2
return $(($1+$2))
}
fSum 5 7
total=$(fSum 3 2)
echo "Return :"$total,$?
|
函数中用到的特殊字符
1
2
3
4
5
6
7
8
9
10
11
12
|
\begin{table}[]
\begin{tabular}{ll}
参数 & 说明 \\\\
\$\# & 传递到脚本的参数个数 \\\\
\$* & 以一个单字符串显示所有向脚本传递的参数 \\\\
$$ & 脚本运行的当前进程ID号 \\\\
\$! & 后台运行的最后一个进程的ID号 \\\\
\$@ & 与\$*相同,但是使用时加引号,并在引号中返回每个参数。 \\\\
\$- & 显示Shell使用的当前选项,与set命令功能相同。 \\\\
\$? & 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
\end{tabular}
\end{table}
|
shell输入输出重定向概括
linux 启动后,会默认打开3个文件描述符,分别是:
- 标准输入 standard input 0
- 正确输出 standard output 1
- 错误输出 error output 2
shell输出重定向主要用向右的尖括号来作为符号>,主要有“>”和“»”两种方式。当使用“>”时,系统会判断右边文件是否存在,如果存在就先删除,并且创建新文件。不存在则直接创建。因此无论左边命令执行是否成功,右边文件都会变为空。当使用“»”操作符时,系统会判断右边文件是否存在,如果不存在,先创建。然后以添加方式打开文件,系统会分配一个文件描述符与左边的标准输出【1】或错误输出【2】绑定。
输出重定向的详细说明如下
\begin{table}[]
\begin{tabular}{ll}
命令格式 & 命令说明 \\
Command filename & 把标准输出重定向到一个文件中 \\
Command filename 2{}&1 & 把标准输出和错误一起重定向到一个文件中 \\
Command 2 filename & 把标准错误重定向到一个文件中 \\
Command 2 {} filename & 把标准输出重定向到一个文件中(追加) \\
Command {} filename2{}&1 & 把标准输出和错误一起重定向到一个文件(追加)
\end{tabular}
\end{table}
1
2
3
|
command 2> /dev/null
#如果command执行出错,将错误的信息重定向到空设备(忽略掉错误输出)
# 不管命令输出是什么 直接丢弃
|
1
2
|
command > out.put 2>&1
#将command执行的标准输出和标准错误重定向到out.put(也就是说不管command执行正确还是错误,输出都打印到out.put)。
|
标准输入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#cat num.txt
1
2
4
3
5
7
6
8
9
#sort < num.txt
1
2
3
4
5
6
7
8
9
|
文件包含
这个类似高级语言中 import
,可以引用其他文件的内容,使得结构更加清晰。有两种方式:使用点号“.”+文件名包含;source+文件名。
1
2
3
|
. filename
# 或者
source filename
|
同样给出一个例子
num1.sh 文件的内容:
num2.sh 文件内容:
1
2
3
|
. ./num1.sh
echo "num1 = "$num1
echo "num2 ="$num2
|
然后执行输出。
调试bash 脚本
使用 -x
来运行shell 脚本
或者使用万能的 printf 大法
一篇文章让你彻底掌握 shell 语言
shell终端编辑命令行快捷键
Ctrl + a 光标移动到行首(Ahead of line),相当于通常的Home键
Ctrl + e 光标移动到行尾(End of line)
多行命令的执行
命令之间的关系就是,并或的关系。
- [ ; ]
如果被分号(;)所分隔的命令会连续的执行下去,就算是错误的命令也会继续执行后面的命令。
1
2
3
4
|
[root@localhost etc]# lld ; echo “ok” ; lok
-bash: lld: command not found
ok
-bash: lok: command not found
|
- [ && ]
如果命令被 && 所分隔,那么命令也会一直执行下去,但是中间有错误的命令存在就不会执行后面的命令,没错就直行至完为止。
1
2
3
|
[root@localhost etc]# echo “ok” && lld && echo “ok”
ok
-bash: lld: command not found
|
- [ || ]
如果每个命令被双竖线 || 所分隔,那么一遇到可以执行成功的命令就会停止执行后面的命令,而不管后面的命令是否正确与否。如果执行到错误的命令就是继续执行后一个命令,一直执行到遇到正确的命令为止。
1
2
3
4
5
|
[root@localhost etc]# echo “ok” || echo “haha”
ok
[root@localhost etc]# lld || echo “ok” || echo “haha”
-bash: lld: command not found
ok
|
shell 适用函数
- shell 中判断是否存在文件夹,如果不存在,那么创建。
1
2
3
4
5
6
7
8
9
10
11
|
#!/bin/sh
DATASET="adityajn105/flickr8k"
DATA_DIR="data/f8k"
if [ -d ${DATA_DIR} ]; then
echo ${DATA_DIR}' exists, please remove it before running the script'
exit 1
fi
mkdir -p ${DATA_DIR}
kaggle datasets download -d ${DATASET}
|
在shell 脚本中 exit 1 表示非正常退出程序(不为 0 就表示程序运行出错),那么 exit 0 表示正常退出
wget download 文件时候可以使用 -O
文件的写入。 使用shell 脚本下载文件,注意shell 是从 $1
开始的
1
2
3
4
5
6
7
8
9
10
11
|
FILE=$1
echo "Note: available models are apple2orange, orange2apple, summer2winter_yosemite, winter2summer_yosemite, horse2zebra, zebra2horse, monet2photo, style_monet, style_cezanne, style_ukiyoe, style_vangogh, sat2map, map2sat, cityscapes_photo2label, cityscapes_label2photo, facades_photo2label, facades_label2photo, iphone2dslr_flower"
echo "Specified [$FILE]"
mkdir -p ./checkpoints/${FILE}_pretrained
MODEL_FILE=./checkpoints/${FILE}_pretrained/latest_net_G.pth
URL=http://efrosgans.eecs.berkeley.edu/cyclegan/pretrained_models/$FILE.pth
wget -N $URL -O $MODEL_FILE
|
set -ex
的使用
写 shell 脚本时候,一般是一个串联的,要求命令是一起执行完。通常来说,习惯于使用 &&
来实现这样的功能。比如以下的例子:
1
2
|
#!/bin/bash
echo 1 && rm non-existent-file && echo 2
|
写成一行,可读性不强。并且, &
之间是无法增加注释的。
set -e
这个参数的含义是,当命令发生错误的时候,停止脚本的执行。
1
2
3
4
|
set -e
echo 1
rm non-existent-file
echo 2
|
进一步可以设置
set -x
-x
参数的作用,是把将要运行的命令用一个 +
标记之后显示出来。
1
2
3
4
|
set -ex
echo 1
rm non-existent-file # which will fail
echo 2
|
所以最后的输出变成以下的样子
1
2
3
4
|
+ echo 1
1
+ rm non-existent-file
rm: non-existent-file: No such file or directory
|