跳转至

Linux Command Line and Shell Scripting Bible

又名《Linux命令行与shell脚本编程大全》,可以配合Hackerrank的shell习题来巩固提高。

第3章 基本的bash shell命令

1583592110567

3.5 文件和目录列表

3.5.1 基本列表功能

# -F命令可以轻松区分文件和目录,在目录的后面加上了正斜线/
$ ls -F
CS-631/  Scheme/   cpp/   myGitBook/  reveal.js/  unpv13e/
Python/  apue.3e/  docs/  photo/      test.txt
# -a把隐藏文件和其他文件都显示出来
# -R递归显示当前目录下的文件和子目录下的文件

3.5.2 显示长列表

# 显示更多相关信息
$ ls -l

3.6 处理文件

3.6.1 创建文件

$ touch [filename]

3.6.2 复制文件

$ cp source destination

如果复制的文件在目标路径下已经存在,可以加上-i选项强制shell询问是否覆盖。

比较文件

//file1.txt
I need to buy apples.
I need to run the laundry.
I need to wash the dog.
I need to get the car detailed.

//file2.txt
I need to buy apples.
I need to do the laundry.
I need to wash the car.
I need to get the dog detailed.

我们使用diff比较他们的不同:

$ diff file1.txt file2.txt
2,4c2,4
< I need to run the laundry.
< I need to wash the dog.
< I need to get the car detailed.
---
> I need to do the laundry.
> I need to wash the car.
> I need to get the dog detailed.

diff描述两个文件不同的方式是告诉我们怎么样改变第一个文件之后与第二个文件匹配。我们看看上面的比较结果中的第一行 2,4c2,4 前面的数字2,4表示第一个文件中的行,中间有一个字母c表示需要在第一个文件上做的操作(a=add,c=change,d=delete),后面的数字2,4表示第二个文件中的行。

2,4c2,4 的含义是:第一个文件中的第[2,4]行(注意这是一个闭合区间,包括第2行和第4行)需要做出修改才能与第二个文件中的[2,4]行相匹配。 接下来的内容则告诉我们需要修改的地方,前面带 < 的部分表示左边文件的第[2,4]行的内容,而带> 的部分表示右边文件的第[2,4]行的内容,中间的 --- 则是两个文件内容的分隔符号。

第11章 构建基本脚本

11.2 创建shell脚本文件

在文件的第一行必须加上

#!/bin/bash

可以使用分毫;将命令放在一行。但是此时脚本文件不具有执行权限,所以:

$ chmod u+x filename.sh
# 第二种方式
$ chmod 777 filename.sh

11.3 显示消息

$ echo The time and date are && date
The time and date are
Sat Mar  7 23:03:28 DST 2020

会发现文本和date的显示信息分别在不同的行,如果想在同一行显示,可以使用-n选项。

$ echo -n 'The time and date are: ' && date
The time and date are: Sat Mar  7 23:04:02 DST 2020

参考链接:https://www.cnblogs.com/anttech/p/11247423.html

这里值得区分一下单引号',双引号"和反引号在echo里的区别。

单引号:强引用,所见即所得,即单引号里面包含什么就输出什么。

双引号:弱引用,输出双引号的所有内容;如果内容中有命令(要反引)、变量、特殊转义,会先把变量、命令、转义字符解析出结果,然后在输出最终内容

反引号:一般用于命令,执行的时候命令会被执行,相当于$(),赋值和输出都要用反引号引起来。

通过几个例子来区分:

$ echo $USER
kylin
$ echo '$USER'
$USER
$ echo "$USER"
kylin
$ echo "`$USER`"
kylin: command not found

11.4 使用变量

11.4.2 用户变量

引用一个变量时需要使用$符号,比如:

#!/bin/bash
days=10
guest='Katie'
echo "$guest checked in &days days ago"
days=5
guest='Jessica'
echo "$guest checked in &days days ago"
$ sudo chmod 777 test.sh
$ ./test.sh

11.4.3 命令替换

11.5.2 输入重定向

$ wc < test.txt

wc命令一般会有3个输出:

  • 文本的行数
  • 文本的词数
  • 文本的字节数

内联重定向:

$ wc << EOF
> test string 1
> test string 2
> test string 3
> EOF
 3  9 42

11.6 管道

有时需要将一个命令的输出作为另一个命令的输入。

11.7 数学运算

1584546798309

注意:许多命令在expr里存在歧义

比如执行

$ expr 1 * 2
expr: syntax error

这是因为在shell里面*另有含义,所以需要转义字符\

$ expr 1 \* 2
2

11.7.2 使用方括号

如果我们在编写程序的过程中,还要注意符号存在歧义,显然会产生很多麻烦,但是使用方括号就不用考虑产生歧义的问题了。

#!/bin/bash
var=$[1*2]
echo $var

11.7.3 浮点数解决方案

有几种解决方案能够克服bash中数学运算的整数限制。最常见的方案是用内建的bash计算器,叫作bc。

要退出bash计算器,你必须输入quit。

$ bc
1 + 2
3
1 / 2
0
3.44 / 5
0

浮点运算是由内建变量scale控制的。必须将这个值设置为你希望在计算结果中保留的小数位数,否则无法得到期望的结果。

$ bc
scale=4
3.44 / 5
.6880

scale变量的默认值是0。在scale值被设置前,bash计算器的计算结果不包含小数位。在将其值设置成4后,bash计算器显示的结果包含四位小数。-q命令行选项可以不显示bash计算器冗长的欢迎信息。

除了普通数字,bash计算器还能支持变量。

$ bc
var = 1     
var2 = 2    
var + var2  
3           
print var
1
quit

在脚本中使用bc

可以用命令替换运行bc命令,并将输出赋给一个变量。基本格式如下:

#!/bin/bash
var=$(echo "scale=4; 3.44 / 5" | bc)
echo The answer is $var

但是这种使用方法还是不够优雅,所以可以使用内联重定向:

#!/bin/bash
var1=10.46
var2=43.67
var3=33.2
var4=71
var5=$(bc << EOF
scale = 4
a1 = ($var1 * $var2)
b1 = ($var3 * $var4)
a1 + b1
EOF
)
echo $var5

11.8 退出脚本

查看退出状态码:

$ date && echo $?
Thu Mar 19 00:15:16 DST 2020
0
$ absd
Command 'absd' not found, did you mean:

  command 'amsd' from deb ion
  command 'afsd' from deb openafs-client

Try: sudo apt install <deb name>
$ echo $?
127

1584548206479

11.8.2 exit命令

默认情况下,shell脚本会以脚本中的最后一个命令的退出状态码退出。

你可以改变这种默认行为,返回自己的退出状态码。exit命令允许你在脚本结束时指定一个退出状态码。

第12章 使用结构化命令

12.1 使用if-then语句

if command
then
    command
fi

bash shell的if-then语句和C++相比,C++在if后的语句会产生一个求值结果为truefalse,但是bash shell会先执行if后面的命令,如果命令的状态码是0,位于then后的命令会被执行,否则不执行。

#!/bin/bash
if pwd
then
    echo "it worded!"   
fi

也存在if-then语句的另一种形式:

if command; then
    command
fi

通过分号把thenif放在同一行,看起来和其他语言更接近。

12.2 if-then-else语句

if command
then
    command
else
    command
fi

12.3 嵌套if

if command1
then
    commands
elif command2
then
    commands
fi

12.4 test命令

if test condition
then 
    command
fi

也可以使用方括号定义测试条件,但是注意第一个方括号后和第二个方括号前必须加上一个空格。

if [ condition ]
then
    command
fi

test可以判断三类条件:

  • 数值比较
  • 字符串比较
  • 文件比较

12.4.1 数值比较

比较 描述
a -eq b 检查a和b是否相等
a -ge b 检查a是否大于等于b
a -gt b 检查a是否大于b
a -le b 检查a是否小于等于b
a -lt b 检查a是否小于b
a -ne b 检查a和b是否相等

不能再test命令中使用浮点值。

12.4.2 字符串比较

比较 描述
s1 = s2 s1和s2是否相同
s1 != s2 s1和s2是否不同
s1 \< s2 s1比s2小
s1 \> s2 s1比s2大
-n s s的长度是否非0
-z s s长度是否为0

注意小于号和大于号要进行转义,否则会被解释为重定向。

另一个问题就是和sort相关,比如下面:

#!/bin/bash
s1=Testing
s2=testing
if [ $s1 \< $s2 ]
then
    echo "$s1 is less than $s2"
else
    echo "$s1 is greater than $s2"
fi
$ ./test.sh
Testing is less than testing

因为比较测试中按照ASCII顺序,大写字母被认为小于小写字母。

$ touch testfile
$ vim testfile

testfile里写入

Testing
testing
$ sort testfile
Testing
testing

sort命令使用的是系统的本地化语言设置中定义的排序顺序。对于英语,本地化设置指定了在排序顺序中小写字母出现在大写字母前。

注意大于号和小于号会产生输入/输出重定向的歧义,所以需要转义字符。

12.4.3 文件比较

1584714776568

  1. 检查目录
#!/bin/bash
# Look before you leap
#
jump_directory=/home/kylin
#
if [ -d $jump_directory ]
then
    echo "The $jump_directory directory exists"
    cd $jump_directory
    ls
else
    echo "The $jump_directory directory does not exist"
fi
#
$ sudo chmod 777 test.sh
$ ./test.sh
The /home/kylin directory exists
12.sh  CS-631  CS631-APUE  Python  Scheme  apue.3e  cpp  docs  myGitBook  photo  reveal.js  unpv13e
  1. 检测对象是否存在
#!/bin/bash
# Check if either a directory or file exists
#
location=$HOME
file_name="sentinel"
#
if [ -e $location ]
then #Directory does exist
    echo "OK on the $location directory."
    echo "Now checking on the file, $file_name."
    #
    if [ -e $location/$file_name ]
    then #File does exist
        echo "OK on the filename"
        echo "Updating Current Date..."
        date >> $location/$file_name
    #
    else #File does not exist
        echo "File does not exist"
        echo "Nothing to update"
    fi
#
else #Directory does not exist
    echo "The $location directory does not exist."
    echo "Nothing to update"
fi

12.5 复合测试条件

[ condition1 ] && [ condition2 ]
[ condition1 ] || [ condition2 ]

12.6 if-then的高级特性

12.6.1 使用双括号

双括号名命令提供了更多的数学操作,对于其他编程语言来讲比较友好。

1584715640445

12.6.2 使用双方括号

双方括号命令提供了针对字符串比较的高级特性。

[[ expression ]]

双方括号里的expression使用了test命令中采用的标准字符串比较.

在模式匹配中,可以定义一个正则表达式

#!/bin/bash
# using pattern matching
#
if [[ $USER == k* ]]
then
    echo "Hello $USER"
else
    echo "Sorry, I do not know you"
fi
$ ./test.sh
Hello kylin

12.7 case命令

case命令会采用列表格式来检查单个变量的多个值。

#!/bin/bash
# using the case command
#
case $USER in
rich | barbara)
    echo "Welcome, $USER"
    echo "Please enjoy your visit";;
testing)
    echo "Special testing account";;
jessica)
    echo "Do not forget to log off when you're done";;
*)
    echo "Sorry, you are not allowed here";;
esac

第13章 更多结构化命令

13.1 for命令

for var in list
do
    commands
done

13.1.2 读取列表中的复杂值

$ cat badtest1
#!/bin/bash
# another example of how not to use the for command
for test in I don't know if this'll work
do
    echo "word:$test"
done
$ ./badtest1
word:I
word:dont know if thisll
word:work
$

shell看到了列表值中的单引号并尝试使用它们来定义一个单独的数据值.

for循环假定每个值都是用空格分割的

13.1.5 更换字段分隔符

bash会将空格、制表、换行作为分隔符。

要解决这个问题,可以在shell脚本中临时更改IFS环境变量的值来限制被bash shell当作字段 分隔符的字符。例如,如果你想修改IFS的值,使其只能识别换行符,那就必须这么做:

IFS=$'\n'
#!/bin/bash
# reading values from a file
file="states"
IFS=$'\n'
for state in $(cat $file)
do
    echo "Visit beautiful $state"
done

如果要指定多个IFS字符,只要将它们在赋值行串起来就行。

IFS=$'\n':;"

这个赋值会将换行符、冒号、分号和双引号作为字段分隔符。如何使用IFS字符解析数据没 有任何限制。

13.1.6 用通配符读取目录

#!/bin/bash
# iterate through all the files in a directory
for file in /home/kylin/*
do
if [ -d "$file" ]
then
    echo "$file is a directory"
elif [ -f "$file" ]
then
    echo "$file is a file"
fi
done
$ ./test.sh
/home/kylin/12.sh is a file
/home/kylin/CS-631 is a directory
/home/kylin/CS631-APUE is a directory
/home/kylin/Python is a directory
/home/kylin/Scheme is a directory
/home/kylin/apue.3e is a directory
/home/kylin/cpp is a directory
/home/kylin/docs is a directory
/home/kylin/myGitBook is a directory
/home/kylin/photo is a directory
/home/kylin/reveal.js is a directory
/home/kylin/unpv13e is a directory

在Linux中,目录名和文件名中包含空格当然是合法的。要适应这种情况,应该将$file变量用双引号圈起来。如果不这么做,遇到含有空格的目录名或文件名时就会有错误产生.

也可以在for命令中列出多个目录通配符,将目录查找和列表合并进同一个for语句。

13.2 C语言风格的for命令

for (( variable assignment ; condition ; iteration process ))

例如:

for (( a = 1; a < 10; a++ ))

有些部分并没有遵循bash shell标准的for命令:

  • 变量赋值可以有空格;
  • 条件中的变量不以美元符开头;
  • 迭代过程的算式未用expr命令格式。
#!/bin/bash
# testing the C-style for loop
for (( i = 1; i <= 10; i++ ))
do
    echo "The next number is $i"
done
#!/bin/bash
# multiple variables
for (( a = 1, b = 10; a <= 10; a++, b-- ))
do
    echo "$a - $b"
done

13.3 while命令

基本格式:

while test-command
do
    command
done

13.4 until命令

#!/bin/bash
# using the until command
var1=100
until [ $var1 -eq 0 ]
do
    echo $var1
    var1=$[ $var1 - 25 ]
done
var=100
until (( $var == 0))
do
    echo $var
    var=$(( $var - 25 ))
done