《Linux命令行大全》读书笔记三:编写shell脚本

第二十四章:编写第一个shell脚本

shell脚本是一个包含一系列的命令的文件,shell读取这个文件,然后执行这些命令。三个步骤:
- 编写脚本
- 使脚本可执行
- 将脚本放置于shell能够发现的地方

脚本的格式,最简单的一个脚本:

#!/bin/bash
# This is our first script
echo 'Hello World!'

将上述脚本保存为hello_world。
可执行权限:

chmod 755 hello_world   //rwxr-xr-x

执行脚本:

./hello_world       //good
hello_world         //无法执行

为什么直接写文件名称无法运行,这是因为脚本的位置,查看Path的内容:

echo $PATH          //执行后可以看到一系列目录

脚本如果在这一系列目录中任意一个,那么就可以直接执行。
~/bin目录是存放一个脚本的理想位置。
更多格式的诀窍:
1. 使用长选项,提高可读性

ls -ad
ls --all --directory
  1. 缩进
    一般的长命令:
find playground \( -type -f -not -perm 0600 -exec chomd 0600 '{}' ';'\) -or \( -type d -not -perm 0700 -exec chmod 0700 '{}' ';' \)

使用缩进后:

find playground \
        \( \
                -type -f \
                -not -perm 0600 \
                -exec chmod 0600 '{}' ';' \
        \) \
        -or \
        \( \
                -type d \
                -not -perm 0700 \
                -exec chmod 0700 '{}' ';' \
        \) \

每行的末尾的\(行连接符)都代表换行,行首的四个\代表转义字符。

第二十五章:启动一个项目

HTML文档:

#!/bin/bash
# Program to output a system information page
echo "<HTML>
        <HEAD>
                <TITLE>Page Title</TITLE>
        </HEAD>
        <BODY>
                <H1>System Information Report </H1>
                Page body.
        </BODY>
</HTML>"

常量和变量:
变量是由字母、数字和下划线组成,名称的首字母必须是字母或者下划线,且名称中不能有空格和标点
常量和变量其实没有区别,只是为了编程方便。
通常使用全大写字母表示常量,小写字母表示变量。例如

foo="yes"
FOO="no"
echo $foo        //yes
echo $foo1       //空值

为常量和变量赋值:注意在赋值时,变量(常量)和等号,等号和值之间不能有空格

a=z     //字符z赋值给变量a
b="a string"        //空格必须用引号括起来
c="a string and $b" //使用其它扩展
d=$(ls -l foo.txt)  //命令的结果
e=$((5*7))      //算术扩展
f="\t\ta string\n"  //转义序列
a=5 b="a string"    //一行为多个变量赋值

在扩展区间,变量名称用花括号括起来:

filename="myfile"
touch $filename
mv $filename ${filename}1   //将myfile文件重命名为myfile1

除了echo可以输出文本,“here脚本”也可以用来输出。

command << token
text
token

command是命令名,token是用来指示嵌入文本结尾的字符串。例如:

#!/bin/bash
# Program to output a system information page
cat << _EOF_
<HTML>
        <HEAD>
                <TITLE>Page Title</TITLE>
        </HEAD>
        <BODY>
                <H1>System Information Report </H1>
                Page body.
        </BODY>
</HTML>
_EOF_

不再使用echo,而是使用cat和“here文档”,特别要注意:在“here文档”中,单引号和双引号都将失去它们在shell中的特殊含义。所以在“here文档”中可以随意嵌入引号。
另外,重定向符<<如果改成<<-,shell就会忽略here文档中的Tab字符,这样就能缩进here文档,从而提高可读性。

#!/bin/bash
# Script to retrieve a file via FTP
FTP_SERVER=ftp.nl.debian.org
FTP_PATH=/debian/dists/lenny/main/installer-i386/current/images/cdrom
REMOTE_FILE=debian-cd_info.tar.gz
ftp -n <<- _EOF_
        open $FTP_SERVER
        user anonymous me@linuxbox
        cd $FTP_PATH
        hash
        get $REMOTE_FILE
        bye
        _EOF_
ls -l $REMOTE_FILE

第二十六章:自顶向下设计

1. shell函数

function name {
        commands
        return
}

或者

name () {
        commands 
        return
}

举个例子:

#!/bin/bash
# shell function

function func {
        echo "step2"
        return
}
# Main
echo "step1"
func
echo "step3"

执行该脚本时,会忽略注释行和函数定义行,而且函数调用必须在函数定义之前。
函数命名规则和变量相同。一个函数必须至少包含一条命令,return(可选)可以满足该要求。
2. 局部变量
定义在shell函数内的变量,用local修饰,一旦shellh函数终止,该变量就不再存在。

#!/bin/bash
# local variables
foo=0 # global variable
func_1 () {
    local foo
    foo=1
    echo "func_1: foo = $foo"
}
func_2 () {
    local foo
    foo=2
    echo "func_2: foo = $foo"
}
echo "global foo = $foo"
func_1
func_2

输出的结果显然为:

global foo = 0
func_1: foo = 1
func_2: foo = 2

3. 保持脚本的运行

第二十七章:流控制:IF分支语句

1. IF语句的格式如下:

if commands; then
        commands
[elif commands; then
        commands...]
[else 
        commands...]
fi

比如:

x=5
if [ $x = 5 ]; then
        echo "x equals 5"
else
        echo "x does not equal 5"
fi

后面会解释中括号的内容。
2. 退出状态
命令执行完之后,会向操作系统发送一个值,称之为退出状态,0~255,0表示成功,其它表示失败。
在执行完一个命令后,输入echo $?,即表示输出上一次命令的退出状态。
另外:linux还内置的两个命令:true表示命令总是执行成功,false表示总是失败。
3. 使用test命令
if连用的命令是:

test expression
if [ expression ]   //注意有空格

常见的文件表达式:

file1 -ef file2 //两个文件通过硬链接指向同一个文件
file1 -nt file2 //file1比file2新
file1 -ot file2 //file1比file2旧
-e file         //文件存在
-d file         //文件存在且为目录
-f file         //文件存在且为普通文件
-L file         //文件存在且为符号链接
-r file         //文件存在且可读
-S file         //文件存在且是一个网络套接字

常见的字符串表达式:

string          //string不为空
-n string       //string长度大于0
-z string       //string的长度为0
string1=string2
string1==string2    //相等
string1!=string2    //不相等
string1<string2     //排序小于

注意:在使用test命令时,<和>必须使用引号括起来,或者是使用反斜杠转义,否则会被shell解释为重定向。

整数表达式:

int1 -eq int2       //equal
int1 -ne int2       //not equal
int1 -le int2       //less and equal
int1 -lt int2       //less than
int1 -ge int2       //greater and equal
int1 -gt int2       //great than

举个例子:

#!/bin/bash
echo -n "Please enter a number: "
read INT
if [ -z "$INT" ]; then
        echo "INT is empty." >&2
        exit 1
fi
if [ $INT -eq 0 ]; then
        echo "INT is zero."
else
        if [ $INT -lt 0 ]; then
                echo "INT is negetive."
        else
                echo "INT is positive."
        fi
        if [ $((INT % 2)) -eq 0]; then
                echo "INT is even."
        else
                echo "INT is odd."
        fi
fi

上述脚本用来判断整数INT是正负数和奇偶数。

4. 更加现代的test命令版本
增强的命令:

[[ expression ]]

该命令增加了新的字符串表达式以及==操作符:

string=~regex

例如:

INT=-5 FILE=foo.bar
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
    ...
fi
if [[ $FILE == foo.* ]]; then
    ...
fi

其中^-?[0-9]+$是一个正则表达式,表示以负号开头,且以0~9中的某一个数字结尾。foo.*即模式匹配,匹配以foo.开头的任意字符串。
5. (())——为整数设计
bash还提供了(())复合命令,专门为整数设计(可以嵌套使用),例如:

INT=-5
if ((INT == 0)); then   //等价于[ INT -eq 0 ]
if (( ((INT % 2)) == 0 ))   //嵌套使用

该命令能够通过变量名称识别变量,因此不需要扩展操作。
6. 组合表达式
表达式之间常常使用逻辑运算符连接:

Operator test [[]]和(())
AND -a &&
OR -o `
NOT ! !

例如:

MIN_VAL=1 MAX_VAL=100 INT=50
if [[ !(INT -ge MIN_VAL) && INT -le MAX_VAL ]];
//等价于[ !\(INT -ge MIN_VAL\) -a INT -le MAX_VAL ]

特别注意:在test命令中,不像[[ ]](()),在bash中有特殊含义的字符,例如:<, >, (, )必须进行转义或者用引号括起来,所以上述等价中的括号进行了转义。

7. 控制运算符:另外一种方式的分支
bash还提供了两种可以执行的分支的控制运算符:&&||,与复合命令中的逻辑运算符类似:

command1 && command2
command1 || command2

例如:

mkdir temp && cd temp

第二十八章:读取键盘输入

shell程序如何与用户交互呢?
1. read——从标准输入读取输入值

read [-options] [variables...]

常见的read选项:

-a array    //将输入值从索引为0的位置开始赋给array
-s          //保密模式,不在屏幕显示输入的字符
-n num     //读取num个字符,不是一整行
-e          //使用Readline处理输入
-p prompt   //使用prompt字符提示用户进行输入

例子:

#!/bin/bash
# read-multipe
echo -n "Please enter one or more values: "
read var1 var2 var3 var4 var5
echo "var1 = '$var1'" 
echo "var2 = '$var2'" 
echo "var3 = '$var3'" 
echo "var4 = '$var4'" 
echo "var5 = '$var5'" 

此脚本为五个变量赋值,当输入少于5个时,剩下的为空,输入大于5个时,多余的都被赋值到最后一个变量中了。如果read后没有任何一个变量,则会为所有的输入分配一个shell变量:REPLY
使用read选项。
使用IFS间隔输入字段:
IFS的默认值包含空格、制表符和换行符,每一种都能把字符彼此间分隔开。例如:

#!/bin/bash
# read-ifs
FILE=/etc/psswd
read -p "Please enter a username >" user_name
file_info=$(grep "^$user_name:" $FILE)
if [ -n "$file_info" ]; then
        IFS=":" read user pw uid gid name home shell <<< "$file_info"
        echo "User =    '$user'"
        echo "UID =     '$uid'"
        echo "GID =     '$gid'"
        echo "Full Name = '$name'"
        echo "Home Dir = '$home'"
        echo "Shell = '$shell'"
else
        echo "No such user '$user_name'" >&2
        exit 1
fi

2. 验证输入
3. 菜单
菜单驱动是一种常见的交互方式,例如:

echo "
Please Select:
1. xxx
2. xxxx
3. xxxxx
"
read -p "Enter selection [0-3]: "
if [[ $REPLY =~ ^[0-3]$ ]]; then
        ...
        if [[ $REPLY == 1 ]]; then
                ...
        elif [[ $REPLY == 2 ]]; then
                ...
        else
                ...
        fi
else
        ...
fi

第二十九章:流控制,WHILE和UNTIL循环

1. while循环:

while commands; do 
        commands
done

例如:输出1~5这5个数字

#!/bin/bash
# while-count
count=1
while [ $count -le 5]; do
    echo $count
    count=$((count + 1))
done
echo "finished."

2. 跳出循环
使用continuebreak
3. until
untilwhile的流程是一样的,只是判断条件恰好相反:

until commands; do 
        commands
done

4. 使用循环读取文件

第三十章:

第三十一章:

第三十二章:

第三十三章:

第三十四章:

第三十五章:

第三十六章:

主要参考自《Linux命令行大全》William E. Shotts, Jr. 著 郭光伟 郝记生 译 以及 Linux命令大全

猜你喜欢

转载自blog.csdn.net/qq_25467397/article/details/81008352
今日推荐