本文是介绍shell 语言的学习笔记。

shell简介

Shell既是一种命令语言,又是一种程序设计语言。

作为命令语言,它可以交互式地解释和执行用户输入的命令;而作为程序设计语言,它可以定义各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。

新建一个文本,扩展名是sh(其实扩展名并不影响脚本执行),但是为了分辨

1
2
#! /bin/bash
echo "Hello world!"

“#!” 是一个约定的标记,告诉系统这个脚本使用什么样的解释器来执行。以下这种执行方式,即使在文件的第一行指定了解释器,那也是不起作用的。

1
/bin/bash test.sh 

只有使用以下方式的时候,才是使用的文件中的shell 脚本。

1
./test.sh 

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

变量类型

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

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

  3. 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中原有内容不会被清除,新内容会追加到文件结尾处

保持原样输出

1
echo '$name\"'

(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 文件的内容:

1
2
num1 =1
num2 =1

num2.sh 文件内容:

1
2
3
. ./num1.sh
echo "num1 = "$num1
echo "num2 ="$num2

然后执行输出。

调试bash 脚本

使用 -x 来运行shell 脚本

1
bash -x script_name

或者使用万能的 printf 大法

一篇文章让你彻底掌握 shell 语言

shell终端编辑命令行快捷键

Ctrl + a 光标移动到行首(Ahead of line),相当于通常的Home键 Ctrl + e 光标移动到行尾(End of line)

多行命令的执行

命令之间的关系就是,并或的关系。

  1. [ ; ]

如果被分号(;)所分隔的命令会连续的执行下去,就算是错误的命令也会继续执行后面的命令。

1
2
3
4
[root@localhost etc]# lld ; echo “ok” ; lok
-bash: lld: command not found
ok
-bash: lok: command not found
  1. [ && ]

如果命令被 && 所分隔,那么命令也会一直执行下去,但是中间有错误的命令存在就不会执行后面的命令,没错就直行至完为止。

1
2
3
[root@localhost etc]# echo “ok” && lld && echo “ok”
ok
-bash: lld: command not found
  1. [ || ]

如果每个命令被双竖线 || 所分隔,那么一遇到可以执行成功的命令就会停止执行后面的命令,而不管后面的命令是否正确与否。如果执行到错误的命令就是继续执行后一个命令,一直执行到遇到正确的命令为止。

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 适用函数

  1. 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