Shell基础

一、构建基本脚本

1.创建shell脚本文件

在创建shell脚本文件时,必须在文件的第一行指定要使用的shell,其格式为:

#!/bin/bash

在通常的shell脚本中,井号(#)用作注释行,shell并不会处理shell脚本中的注释行。然而,shell脚本文件的第一行是个例外,#后面的惊叹号会告诉shell用哪个shell来运行脚本:

#!/bin/bash
# This script displays the date
date

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test
2020年 09月 03日 星期四 23:37:55 CST

2.显示消息

使用echo命令显示字符串:

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ echo hello shell
hello shell
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ echo "It's shell"
It's shell
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ echo 'It says "shell"'
It says "shell"

可以将echo语句添加到shell脚本中任何需要显示额外信息的地方:

#!/bin/bash
# This script displays the date
echo "The time and date are:"
date
echo "end"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test
The time and date are:
2020年 09月 03日 星期四 23:47:43 CST
end

如果想把文本字符串和命令输出显示在同一行中,可以用echo语句的-n参数:

#!/bin/bash
# This script displays the date
echo -n "The time and date are:"
date
echo "end"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test
The time and date are:2020年 09月 04日 星期五 00:07:34 CST
end

3.使用变量

环境变量

shell维护着一组环境变量,用来记录特定的系统信息。比如系统的名称、登录到系统上的用户名、用户的系统ID(也称为UID)、用户的默认主目录以及shell查找程序的搜索路径。可以用set命令来显示一份完整的当前环境变量列表。在脚本中,你可以在环境变量名称之前加上美元符($)来使用这些环境变量:

#!/bin/bash
echo "User info for userid: $USER"
echo UID: $UID
echo HOME: $HOME

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test
User info for userid: zhangwenming
UID: 1009
HOME: /home/zhangwenming

要显示美元符,必须在它前面放置一个反斜线:

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ echo This cost \$99
This cost $99

用户变量

除了环境变量,shell脚本还允许在脚本中定义和使用自己的变量。定义变量允许临时存储数据并在整个脚本中使用,从而使shell脚本看起来更像一个真正的计算机程序。与系统变量类似,用户变量可通过美元符引用:

#!/bin/bash
days=10
guest="shell"
echo "$guest checked in $days days ago"
days=5
guest="python"
echo "$guest checked in $days days ago"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test
shell checked in 10 days ago
python checked in 5 days ago

命令替换

shell脚本中最有用的特性之一就是可以从命令输出中提取信息,并将其赋给变量,有两种方法:

  • 反引号字符(`)
  • $( )格式
#!/bin/bash
testing1=`date`
testing2=$(date +%y%m%d)
echo "$testing1"
echo "$testing2"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test
2020年 09月 04日 星期五 00:54:43 CST
200904

4.重定向输入和输出

输出重定向

最基本的重定向将命令的输出发送到一个文件中。bash shell用大于号(>)来完成这项功能:

#!/bin/bash
testing1=`date`
testing2=$(date +%y%m%d)
echo "$testing1"
echo "$testing2"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test > output
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ cat output
2020年 09月 04日 星期五 02:10:53 CST
200904

有时你可能并不想覆盖文件原有内容,而是想要将命令的输出追加到已有文件中,可以用双大于号(>>)来追加数据:

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test >> output
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ cat output
2020年 09月 04日 星期五 02:12:35 CST
200904
2020年 09月 04日 星期五 02:13:46 CST
200904

输入重定向

输入重定向符号是小于号(<):

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ cat output
2020年 09月 04日 星期五 02:12:35 CST
200904
2020年 09月 04日 星期五 02:13:46 CST
200904
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ wc < output
  4  14 100

wc命令可以对数据中的文本进行计数。默认情况下,它会输出3个值:

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

5.管道

管道被放在命令之间,将一个命令的输出重定向到另一个命令中:

command1 | command2

Linux系统实际上会同时运行这两个命令,在系统内部将它们连接起来。在第一个命令产生输出的同时,输出会被立即送给第二个命令。数据传输不会用到任何中间文件或缓冲区:

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ cat output | wc
      4      14     100

6.执行数学运算

expr命令

expr命令允许在命令行上处理数学表达式,但是特别笨拙:

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ expr 5 + 7
12

在shell脚本中使用expr命令:

#!/bin/bash
var1=10
var2=20
var3=$(expr $var2 / $var1)
echo The result is $var3

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test
The result is 2

使用方括号

在bash中,在将一个数学运算结果赋给某个变量时,可以用美元符和方括号( $[ operation ] )将数学表达式围起来:

#!/bin/bash
var1=10
var2=20
var3=$[($var2 / $var1) * 10]
echo The result is $var3

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test
The result is 20

浮点解决方案

bash计算器实际上是一种编程语言,它允许在命令行中输入浮点表达式,然后解释并计算该表达式,最后返回结果:

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
5 * 1.2
6.0

在脚本中使用bc,基本格式如下:

variable=$(echo "options; expression" | bc)
#!/bin/bash
var1=$(echo "scale=4; 3.44 / 5" | bc)
echo The result is $var1

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test
The result is .6880

7.退出脚本

查看退出状态码

Linux提供了一个专门的变量$?来保存上个已执行命令的退出状态码:

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ date
2020年 09月 04日 星期五 04:06:55 CST
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ echo $?
0

exit命令

默认情况下,shell脚本会以脚本中的最后一个命令的退出状态码退出。exit命令允许你在脚本结束时指定一个退出状态码:

#!/bin/bash
var1=$(echo "scale=4; 3.44 / 5" | bc)
echo The result is $var1
exit 99

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test
The result is .6880
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ echo $?
99

二、使用结构化命令

1.使用if-then语句

最基本的结构化命令就是if-then语句,格式如下:

if command
then 
    commands
fi

在其他编程语言中,if语句之后的对象是一个等式,这个等式的求值结果为TRUE或FALSE。但bash shell的if语句并不是这么做的。bash shell的if语句会运行if后面的那个命令,如果该命令的退出状态码是0(该命令成功运行),位于then部分的命令就会被执行。如果该命令的退出状态码是其他值,then部分的命令就不会被执行,bash shell会继续执行脚本中的下一个命令:

#!/bin/bash
if pwd
then
    echo "It worked"
fi

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test
/home/zhangwenming/workspace/Tomorrow
It worked

2.if-then-else语句

if command
then 
    commands
else
    commands
fi

3.嵌套if

if command1
then 
    command set 1
elif command2
then
    command set 2
elif command3
then
    command set 3
fi

4.test命令

test命令提供了在if-then语句中测试不同条件的途径。如果test命令中列出的条件成立,test命令就会退出并返回退出状态码0,这样if-then语句就与其他编程语言中的if-then语句以类似的方式工作了。如果条件不成立,test命令就会退出并返回非零的退出状态码,这使得if-then语句不会再被执行。test命令的格式非常简单:

test condition

condition是test命令要测试的一系列参数和值。当用在if-then语句中时,test命令看起来是这样的:

if test condition
then
    commands
fi

如果不写test命令的condition部分,它会以非零的退出状态码退出,并执行else语句块:

#!/bin/bash
if test
then
    echo "It excute then"
else
    echo "It excute else"
fi

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test
It excute else

可以使用test命令确定变量中是否有内容:

#!/bin/bash
variable="Full"
if test variable
then
    echo "It excute then"
else
    echo "It excute else"
fi

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test
It excute then

bash shell提供了另一种条件测试方法,无需在if-then语句中声明test命令:

if [ condition ]
then
    commands
fi

方括号定义了测试条件。注意,第一个方括号之后和第二个方括号之前必须加上一个空格,否则就会报错。

test命令可以判断三类条件:

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

数值比较

比较 描述
n1 -eq n2 检查n1是否与n2相等
n1 -ge n2 检查n1是否大于或等于n2
n1 -gt n2 检查n1是否大于n2
n1 -le n2 检查n1是否小于或等于n2
n1 -lt n2 检查n1是否小于n2
n1 -ne n2 检查n1是否不等于n2
#!/bin/bash
var1=10
var2=20
if [ $var1 -gt $var2 ]
then
    echo ">"
else
    echo "<="
fi

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test
<=

字符串比较

比较 描述
str1 = str2 检查str1是否和str2相同
str1 != str2 检查str1是否和str2不同
str1 < str2 检查str1是否比str2小
str1 > str2 检查str1是否比str2大
-n str1 检查str1的长度是否非0
-z str1 检查str1的长度是否为0
#!/bin/bash
var1=hello
var2=world
if [ $var1 \> $var2 ]
then
    echo ">"
else
    echo "<="
fi

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test
<=

文件比较

文件比较是shell编程中最为强大、也是用得最多的比较形式,它允许你测试Linux文件系统上文件和目录的状态。

比较 描述
-d file 检查file是否存在并是一个目录
-e file 检查file是否存在
-f file 检查file是否存在并是一个文件
-r file 检查file是否存在并可读
-s file 检查file是否存在并非空
-w file 检查file是否存在并可写
-x file 检查file是否存在并可执行
-O file 检查file是否存在并属当前用户所有
-G file 检查file是否存在并且默认组与当前用户相同
file1 -nt file2 检查file1是否比file2新
file1 -ot file2 检查file1是否比file2旧
#!/bin/bash
var=/home/zhangwenming
if [ -d $var ]
then
    echo "directory"
else
    echo "not directory"
fi

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
directory

5.复合测试条件

if-then语句允许你使用布尔逻辑来组合测试,有两种布尔运算符可用:

  • [ condition1 ] && [ condition2 ]
  • [ condition1 ] || [ condition2 ]
#!/bin/bash
var=/home/zhangwenming

if [ -d $var ] && [ -w $var ]
then
    echo "directory can write"
else
    echo "not directory or can not write"
fi

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
directory can write

6.if-then的高级特性

使用双括号

双括号命令的格式如下:

(( expression ))

expression可用是任意的数学赋值或比较表达式。除了test命令使用的标准数学运算符,还会用到其他运算符:

符号 描述
val++ 后增
val-- 后减
++val 先增
--val 先减
! 逻辑求反
~ 位求反
** 幂运算
<< 左位移
>> 右位移
& 位布尔和
| 位布尔或
&& 逻辑和
|| 逻辑或
#!/bin/bash
var1=10

if (( $var1 ** 2 > 90 ))
then
    (( var2 = var1 ** 2 ))
    echo "result is: $var2"
else
    (( var2 = var1 * 2 ))
    echo "result is: $var2"
fi

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
result is: 100

注意,不需要将双括号中表达式里大于号转义。这是双括号命令提供的另一个高级特性。

使用双方括号

双方括号命令的格式如下:

[[ expression ]]

双方括号里的expression使用了test命令中采用的标准字符串比较。但它提供了test命令未提供的另一个特性——模式匹配。在模式匹配中,可以定义一个正则表达式来匹配字符串值:

#!/bin/bash
if [[ $USER == zhang* ]]
then
    echo "hello $USER"
else
    echo "Sorry, I do not know you"
fi

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
hello zhangwenming

7.case命令

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

case variable in
pattern1 | pattern2) commands1;;
pattern3) commands2;;
*) default commands;;
esac

case命令会将指定的变量与不同模式进行比较。如果变量和模式是匹配的,那么shell会执行为该模式指定的命令:

#!/bin/bash
case $USER in
li*)
  echo "Welcome, li";;
huang* | zhang*)
  echo "Welcome, huang or zhang";;
*)
  echo "Sorry, you are not allowed here";;
esac

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
Welcome, huang or zhang

三、更多的结构化命令

1.for命令

bash shell中for命令的基本格式:

for var in list
do 
    commands
done

读取列表中的值:

#!/bin/bash
for test in Android Java Kotin Python 
do
  echo "current is: $test"
done

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
current is: Android
current is: Java
current is: Kotin
current is: Python

读取列表中的复杂值:

#!/bin/bash
for test in I don\'t know if "this'll" work
do
  echo "current is: $test"
done

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
current is: I
current is: don't
current is: know
current is: if
current is: this'll
current is: work

从变量读取列表:

#!/bin/bash
list="Android Java Kotin Python"
for test in $list "Shell"
do
  echo "current is: $test"
done

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
current is: Android
current is: Java
current is: Kotin
current is: Python
current is: Shell

从命令读取值:

#!/bin/bash
file="output"
for test in $(cat $file)
do
  echo "current is: $test"
done

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
current is: 123
current is: 456
current is: abc

更改字段分隔符:

默认情况下,bash shell会将下列字符当做字段分隔符:

  • 空格
  • 制表符
  • 换行符

如果bash shell在数据中看到了这些字符中的任意一个,它就会假定这表明了列表中一个新数据字段的开始。可以在shell脚本中临时更改IFS环境变量的值来限制被bash shell当作字段分隔符的字符。例如,修改IFS的值,使其只能识别换行符:

#!/bin/bash
file="output"
IFS=$'\n'
for test in $(cat $file)
do
  echo "current is: $test"
done

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
current is: 123 456
current is: abc

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

IFS=$'\n':;" 

这个赋值会将换行符、冒号、分号和双引号作为字段分隔符。

用通配符读取目录:

#!/bin/bash
file="output"
IFS=$'\n'
for file in /home/zhangwenming/workspace/Tomorrow/NEW_ZWM/*
do
    if [ -d "$file" ]
    then
      echo "$file is a directory"
    elif [ -f "$file" ]
    then
      echo "$file is a file"
    fi  
done

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
/home/zhangwenming/workspace/Tomorrow/NEW_ZWM/test_one is a file
/home/zhangwenming/workspace/Tomorrow/NEW_ZWM/test_two is a file
/home/zhangwenming/workspace/Tomorrow/NEW_ZWM/test.txt is a file

在Linux中,目录名和文件名中包含空格当然是合法的。要适应这种情况,应该将$file变量用双引号圈起来。

2.C语言风格的for命令

#!/bin/bash
for (( a=1, b=10; a<=10; a++, b--))
do
    echo "$a - $b"
done

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
1 - 10
2 - 9
3 - 8
4 - 7
5 - 6
6 - 5
7 - 4
8 - 3
9 - 2
10 - 1

3.while命令

while的基本格式

while test command
do
    other commands
done

while命令中定义的test command和if-then语句中的格式一模一样。可以使用任何普通的bash shell命令,或者用任何普通的bash shell命令,或者用test命令进行条件测试,比如测试变量值:

#!/bin/bash
var1=5
while [ $var1 -gt 0 ]
do
    echo $var1
    var1=$[ $var1 - 1 ]
done

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
5
4
3
2
1

使用多个测试命令

while命令允许你在while语句行定义多个测试命令。只有最后一个测试命令的退出状态码会被用来决定什么时候结束循环:

#!/bin/bash
var1=5
while echo $var1
      [ $var1 -gt 0 ]
do
    echo "This is inside the loop"
    var1=$[ $var1 - 1 ]
done

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
5
This is inside the loop
4
This is inside the loop
3
This is inside the loop
2
This is inside the loop
1
This is inside the loop
0

4.until命令

until命令要求你指定一个通常返回非零退出状态码的测试命令。只有测试命令的退出状态码不为0,bash shell才会执行循环中列出的命令。一旦测试命令返回了退出状态码0,循环就结束。until命令的格式如下:

until test command
do 
    other commands
done
#!/bin/bash
var1=5
until [ $var1 -le 0 ]
do
    echo "current is: $var1"
    var1=$[ $var1 - 1 ]
done

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
current is: 5
current is: 4
current is: 3
current is: 2
current is: 1

和while命令类似,你可以在until命令语句中放入多个测试条件,只有最后一个命令的退出状态码决定了bash shell是否执行已定义的other commands:

#!/bin/bash
var1=5
until echo $var1
      [ $var1 -le 0 ]
do
    echo "inside the loop"
    var1=$[ $var1 - 1 ]
done

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
5
inside the loop
4
inside the loop
3
inside the loop
2
inside the loop
1
inside the loop
0

5.嵌套循环

#!/bin/bash
for (( a=1; a<=3; a++ ))
do
    echo "start loop $a:"
    for (( b=1; b<=3; b++ ))
    do
        echo "  inside the loop $b"
    done
done 

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
start loop 1:
  inside the loop 1
  inside the loop 2
  inside the loop 3
start loop 2:
  inside the loop 1
  inside the loop 2
  inside the loop 3
start loop 3:
  inside the loop 1
  inside the loop 2
  inside the loop 3

6.循环处理文件数据

#!/bin/bash
IFS=$'\n'
for entry in $(cat output)
do
    echo "value is $entry"
done

abczhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
value is 123 456
value is abc

7.控制循环

break命令

跳出单个循环:

#!/bin/bash
for (( a=1; a<=3; a++ ))
do
    echo "current is $a"
    if [ $a -eq 2 ]
    then
        break
    fi
done 

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
current is 1
current is 2

跳出内部循环:

#!/bin/bash
for (( a=1; a<=3; a++ ))
do
    echo "start loop $a:"
    for (( b=1; b<=3; b++ ))
    do
        echo "  inside the loop $b"
        if [ $b -eq 2 ]
        then
            break
        fi
    done
done 

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
start loop 1:
  inside the loop 1
  inside the loop 2
start loop 2:
  inside the loop 1
  inside the loop 2
start loop 3:
  inside the loop 1
  inside the loop 2

跳出外部循环:

break命令接受单个命令行参数值:break n,其中n指定了要跳出的循环层级,n为1表明跳出的是当前的循环,n为2表明会停止下一级的外部循环:

#!/bin/bash
for (( a=1; a<=3; a++ ))
do
    echo "start loop $a:"
    for (( b=1; b<=3; b++ ))
    do
        echo "  inside the loop $b"
        if [ $b -eq 2 ]
        then
            break 2
        fi
    done
done 

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
start loop 1:
  inside the loop 1
  inside the loop 2

continue命令

continue命令可以提前中止某次循环中的命令,但并不会完全终止整个循环:

#!/bin/bash
for (( a=1; a<=3; a++ ))
do
    echo "start loop $a:"
    for (( b=1; b<=3; b++ ))
    do
        if [ $b -eq 2 ]
        then
            continue
        fi
        echo "  inside the loop $b"
    done
done 

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
start loop 1:
  inside the loop 1
  inside the loop 3
start loop 2:
  inside the loop 1
  inside the loop 3
start loop 3:
  inside the loop 1
  inside the loop 3

8.处理循环的输出

在shell脚本中,你可以对循环的输出使用管道或进行重定向,这可以通过在done命令之后添加一个处理命令来实现:

#!/bin/bash
for (( a=1; a<=3; a++ ))
do
    echo "start loop $a:"
    for (( b=1; b<=3; b++ ))
    do
        if [ $b -eq 2 ]
        then
            continue
        fi
        echo "  inside the loop $b"
    done
done > output

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ cat output
start loop 1:
  inside the loop 1
  inside the loop 3
start loop 2:
  inside the loop 1
  inside the loop 3
start loop 3:
  inside the loop 1
  inside the loop 3

四、处理用户输入

1.命令行参数

向shell脚本传递数据的最基本方法是使用命令行参数。命令行参数允许在运行脚本时向命令行添加数据。bash shell会将一些称为位置参数的特殊变量分配给输入到命令行中的所有参数,这也包括shell所执行的脚本名称。位置参数是标准的数字:0是程序名,1是第一个参数,$2是第二个参数,以此类推。

#!/bin/bash
echo "position 0: $0"
echo "position 1: $1"
echo "position 2: $2"
echo "position 3: $3"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh hello shell perfect
position 0: ./test.sh
position 1: hello
position 2: shell
position 3: perfect

在使用参数前一定要检查其中是否存在数据:

#!/bin/bash
echo "position 0: $0"
echo "position 1: $1"
echo "position 2: $2"
if [ -n "$s2" ]
then
  echo "position 3: $3"
else
  echo "position 3 no value"
fi

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh hello shell
position 0: ./test.sh
position 1: hello
position 2: shell
position 3 no value

2.特殊参数变量

参数统计

特殊变量$#含有脚本运行时携带的命令行参数的个数,可以在脚本中任何地方使用这个特殊变量,就跟普通变量一样:

#!/bin/bash
echo "position 0: $0"
echo "position 1: $1"
echo "position 2: $2"
if [ -n "$s2" ]
then
  echo "position 3: $3"
else
  echo "position 3 no value"
fi
echo "total param count: $#"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh hello shell
position 0: ./test.sh
position 1: hello
position 2: shell
position 3 no value
total param count: 2

抓取所有的数据

*和@变量可以用来轻松访问所有参数,这个两个变量包含了命令行中出现的每一参数值。

*变量会将命令行上提供的所有参数当做一个单词保存。@变量会将命令行上提供的所有参数当作同一字符串中的多个独立的单词,这样你就能够遍历所有的参数值,得到每一个参数,通常通过for命令完成。

#!/bin/bash
for param in "$*"
do
    echo "* is: $param"
done
for param in "$@"
do
    echo "@ is: $param"
done

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh hello shell
* is: hello shell
@ is: hello
@ is: shell

3.获得用户输入

基本的读取

read命令从标准输入(键盘)或另一个文件描述符中接受输入。在收到输入后,read命令会将数据放进一个变量:

#!/bin/bash
echo -n "Enter you name: "
read name
echo "Hello $name, welcome to my program."

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
Enter you name: Tomy
Hello Tomy, welcome to my program.

read命令包含了-p选项,允许你直接在read命令行指定提示符:

#!/bin/bash
read -p "Please enter your age: " age
days=$[ $age * 365 ]
echo "That makes you over $days days old"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
Please enter your age: 10
That makes you over 3650 days old

使用多个变量:

#!/bin/bash
read -p "Please enter your name and age: " name age
days=$[ $age * 365 ]
echo "$name, you over $days days old"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
Please enter your name and age: Android 11
Android, you over 4015 days old

超时

可以用-t选项来指定一个计时器,-t选项指定了read命令等待输入的秒数。当计时器过期后,read命令会返回一个非零退出状态码:

#!/bin/bash
if read -t 5 -p "Please enter your name and age: " name age
then
    days=$[ $age * 365 ]
    echo "$name, you over $days days old"
else
    echo
    echo "Sorry, too slow"
fi

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
Please enter your name and age:
Sorry, too slow

也可以不对输入过程计时,而是让read命令来统计输入的字符数,当输入的字符达到预设的字符数时,就自动退出,将输入的数据赋给变量:

#!/bin/bash
read -n1 -p "Do you want to continue [Y/N]? " answer
case $answer in
Y | y) echo
       echo "fine, continue on...";;
N | n) echo
       echo "OK, goodbye"
       exit;;
esac
echo "script end"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
Do you want to continue [Y/N]? n
OK, goodbye

隐藏方式读取

-s选项可以避免在read命令中输入的数据出现在显示器上(实际上,数据会被显示,只是read命令会将文本颜色设成跟背景色一样):

#!/bin/bash
read -s -p "Enter you password: " pass
echo
echo "your password is: $pass"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
Enter you password:
your password is: tomorrow

从文件中读取

也可以用read命令来读取Linux系统上文件里保存的数据。每次调用read命令,它都会从文件中读取一行文本。当文件中在没有内容时,read命令会退出并返回非零退出状态码:

#!/bin/bash
count=1
cat output | while read line
do 
    echo "Line $count: $line"
    count=$[$count + 1]
done
echo "read file done"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
Line 1: start loop 1:
Line 2: inside the loop 1
Line 3: inside the loop 3
Line 4: start loop 2:
Line 5: inside the loop 1
Line 6: inside the loop 3
Line 7: start loop 3:
Line 8: inside the loop 1
Line 9: inside the loop 3
read file done

五、呈现数据

1.理解输入和输出

标准文件描述符

Linux系统将每个对象当作文件处理。这包括输入和输出进程。Linux用文件描述符(file descriptor)来标识每个文件对象。文件描述符是一个非负整数,可以唯一标识会话中打开的文件。每个进程一次最多可以有几个文件描述符。出于特殊目的,bash shell保留了前三个文件描述符(0、1和2):

文件描述符 缩写 描述
0 STDIN 标准输入
1 STDOUT 标准输出
2 STDERR 标准错误

STDIN文件描述符代表shell的标准输入。对终端界面来说,标准输入是键盘。shell从STDIN文件描述符对应的键盘获得输入,在用户输入时处理每个字符。在使用输入重定向符号(<)时,Linux会用重定向指定的文件来替换标准输入文件描述符。它会读取文件并提取数据,就如同它是键盘上键入的。

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ cat
Hello shell
Hello shell
^C
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ cat < output
start loop 1:
  inside the loop 1
  inside the loop 3
start loop 2:
  inside the loop 1
  inside the loop 3
start loop 3:
  inside the loop 1
  inside the loop 3

STDOUT文件描述符代表shell的标准输出。在终端界面上,标准输出就是终端显示器。shell的所有输出(包括shell中运行的程序和脚本)会被定向到标准输出中,也就是显示器。通过输出重定向符号,通常会显示到显示器的所有输出会被shell重定向到指定的重定向文件。你也可以将数据追加到某个文件,这可以用>>符号来完成。

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ date
2020年 09月 04日 星期五 21:32:27 CST
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ date >> output
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ cat output
start loop 1:
  inside the loop 1
  inside the loop 3
start loop 2:
  inside the loop 1
  inside the loop 3
start loop 3:
  inside the loop 1
  inside the loop 3
2020年 09月 04日 星期五 21:32:47 CST

shell通过特殊的STDERR文件描述符来处理错误消息。STDERR文件描述符代表shell的标准错误输出。shell或shell中运行的程序和脚本出错时生成的错误消息都会发送到这个位置。默认情况下,STDERR文件描述符会和STDOUT文件描述符指向同样的地方(尽管分配给它们的文件描述符值不同)。也就是说,默认情况下,错误消息也会输出到显示器输出中。

重定向错误

只重定向错误:

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ls -al badfile 2> output
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ cat output
ls: cannot access badfile: No such file or directory

重定向错误和数据:

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ls -al newfile badfile 2> errout 1> output
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ cat errout
ls: cannot access badfile: No such file or directory
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ cat output
-rw-rw-r-- 1 zhangwenming zhangwenming 0  9月  3 23:02 newfile

将STDERR和STDOUT的输出重定向到同一个输出文件:

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ls -al newfile badfile &> output
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ cat output
ls: cannot access badfile: No such file or directory
-rw-rw-r-- 1 zhangwenming zhangwenming 0  9月  3 23:02 newfile

2.在脚本中重定向输出

临时重定向

可以将单独的一行输出重定向到STDERR:

#!/bin/bash
echo "This is error output" >&2
echo "This is normal output"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh 2> errout
This is normal output
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ cat errout
This is error output

永久重定向

可以用exec命令告诉shell在脚本执行期间重定向某个特定文件描述符:

#!/bin/bash
exec 2> errout
echo "hello shell"
exec 1>output
echo "this is normal output"
echo "this is err output" >&2

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
hello shell
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ cat output
this is normal output
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ cat errout
this is err output

3.在脚本中重定向输入

exec命令允许你将STDIN重定向到Linux系统上的文件中:

#!/bin/bash
exec 0< output
count=1
while read line
do
    echo "Line #$count: $line"
    count=$[ $count + 1 ]
done

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
Line #1: 123 456
Line #2: abc

六、控制脚本

1.处理信号

Linux利用信号与运行在系统中的进程进行通信。

生成信号

中断进程:CTRL+C组合键会发送SIGINT信号,停止shell中当前运行的进程。

暂停进程:CTRL+Z组合键会生成一个SIGTSTP信号,停止shell中运行的任何进程。

停止进程跟终止进程不同:停止进程会让程序继续保留在内存中,并能从上次停止的位置继续运行。

捕获信号

trap命令允许你来指定shell脚本要监看并从shell中拦截的Linux信号。如果脚本收到了trap命令中列出的信号,该信号不再由shell处理,而是交由本地处理。trap命令的格式是:

trap commands signals
#!/bin/bash
trap "echo ' Sorry! I have trapped Ctrl-C'" SIGINT
sleep 10
echo "end of the script"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
^C Sorry! I have trapped Ctrl-C
end of the script

捕获脚本退出

#!/bin/bash
trap "echo Goodbye..." EXIT
sleep 10
echo "end of script"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
end of script
Goodbye...

2.以后台模式运行脚本

后台运行脚本

#!/bin/bash
trap "echo Goodbye..." EXIT
sleep 10
echo "end of script"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh &
[1] 114907
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ps -l
F S   UID    PID   PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1009 114866 114819  0  80   0 -  6799 wait   pts/13   00:00:00 bash
0 S  1009 114907 114866  0  80   0 -  4158 wait   pts/13   00:00:00 test.sh
0 S  1009 114908 114907  0  80   0 -  2851 hrtime pts/13   00:00:00 sleep
0 R  1009 114909 114866  0  80   0 -  3557 -      pts/13   00:00:00 ps
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ end of script
Goodbye...

[1]+  Done                    ./test.sh
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ps -l
F S   UID    PID   PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1009 114866 114819  0  80   0 -  6799 wait   pts/13   00:00:00 bash
0 R  1009 114911 114866  0  80   0 -  3557 -      pts/13   00:00:00 ps

运行多个后台作业

#!/bin/bash
trap "echo Goodbye..." EXIT
sleep 10
echo "end of script"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh &
[1] 114919
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh &
[2] 114921
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ps -l
F S   UID    PID   PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1009 114866 114819  0  80   0 -  6799 wait   pts/13   00:00:00 bash
0 S  1009 114919 114866  0  80   0 -  4158 wait   pts/13   00:00:00 test.sh
0 S  1009 114920 114919  0  80   0 -  2851 hrtime pts/13   00:00:00 sleep
0 S  1009 114921 114866  0  80   0 -  4158 wait   pts/13   00:00:00 test.sh
0 S  1009 114922 114921  0  80   0 -  2851 hrtime pts/13   00:00:00 sleep
0 R  1009 114924 114866  0  80   0 -  3557 -      pts/13   00:00:00 ps
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ end of script
Goodbye...
end of script
Goodbye...

[1]-  Done                    ./test.sh
[2]+  Done                    ./test.sh
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ps -l
F S   UID    PID   PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1009 114866 114819  0  80   0 -  6799 wait   pts/13   00:00:00 bash
0 R  1009 114925 114866  0  80   0 -  3557 -      pts/13   00:00:00 ps

3.作业控制

查看作业

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh &
[1] 114941
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ jobs -l
[1]+ 114941 Running                 ./test.sh &
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ kill 114941
Goodbye...
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ jobs -l
[1]+ 114941 Terminated              ./test.sh

重启停止的作业

要以后台模式重启一个作业,可用bg命令加上作业号:

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
^Z
[1]+  Stopped                 ./test.sh
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ bg 1
[1]+ ./test.sh &
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ end of script
Goodbye...

[1]+  Done                    ./test.sh

要以前台模式重启作业,可用带有作业号的fg命令:

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
^Z
[1]+  Stopped                 ./test.sh
zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ fg 1
./test.sh
end of script
Goodbye...

七、创建函数

1.基本的脚本函数

创建函数

有两种格式可用用来在bash shell脚本中创建函数。第一种格式采用关键字function,后跟分配给该代码块的函数名:

function name {
    commands
}

第二种格式更接近于其他编程语言中定义函数的方式:

name() {
commands
}

使用函数

#!/bin/bash

function func1 {
  echo "This is an example of a function"
}

echo "call function start"
func1
func1
echo "call function end"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
call function start
This is an example of a function
This is an example of a function
call function end

2.返回值

默认退出状态码

默认情况下,函数的退出状态码是函数中最后一条命令返回的退出状态码。在函数执行结束后,可以用标准变量$?来确定函数的退出状态码:

#!/bin/bash

function func1 {
  echo "This is an example of a function"
}

echo "call function start"
func1
echo "The exit status is: $?"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
call function start
This is an example of a function
The exit status is: 0

使用return命令

bash shell使用return命令来退出函数并返回特定的退出状态码。return命令允许指定一个整数值来定义函数的退出状态码,从而提供了一种简单的途径来编程设定函数的特定退出状态码:

#!/bin/bash

function func1 {
  echo "This is an example of a function"
  return 99
}

echo "call function start"
func1
echo "The exit status is: $?"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
call function start
This is an example of a function
The exit status is: 99

使用函数输出

获取任何类型的函数输出,并将其保存到变量中:

#!/bin/bash

function func1 {
  echo "This is an example of a function"
}

result=$(func1)
echo "The exit status is: $result"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
The exit status is: This is an example of a function

3.在函数中使用变量

向函数传递参数

#!/bin/bash

function func1 {
  echo "param1 is $1"
  echo "param2 is $2"
}

func1 hello shell

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
param1 is hello
param2 is shell

在函数中处理变量

全局变量:默认情况下,你在脚本中定义的任何变量都是全局变量。在函数外定义的变量可以函数内正常访问。

#!/bin/bash

function func1 {
  value=$[ $value * 2 ]
}

value=99
func1
echo "result is: $value"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
result is: 198

局部变量:函数内部使用的任何变量都可以被声明成局部变量。

#!/bin/bash

function func1 {
  local temp=$[ $value * 2 ]
  value=$[ $temp + 100 ]
}

value=99
func1
echo "result is: $value"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
result is: 298

4.数组变量和函数

向函数传递数组参数

#!/bin/bash

function func1 {
  local sum=0
  local newarray
  newarray=($(echo "$@"))
  for value in ${newarray[*]}
  do
    sum=$[ $sum + $value ]
  done
    echo $sum
}

myarray=(1 2 3 4 5)
echo "The original array is: ${myarray[*]}"
arg1=$(echo ${myarray[*]})
result=$(func1 $arg1)
echo "result is: $result"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
The original array is: 1 2 3 4 5
result is: 15

从函数返回数组

#!/bin/bash

function func1 {
  local newarray
  newarray=($(echo "$@"))
  elements=$[ $# - 1 ]
  for (( i=0; i<=$elements; i++ ))
  {
    newarray[$i]=$[ ${newarray[$i]} * 2 ]
  }
  echo ${newarray[*]}
}

myarray=(1 2 3 4 5)
echo "The original array is: ${myarray[*]}"
arg1=$(echo ${myarray[*]})
result=($(func1 $arg1))
echo "The original array is: ${result[*]}"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
The original array is: 1 2 3 4 5
The original array is: 2 4 6 8 10

5.函数递归

#!/bin/bash

function factorial {
    if [ $1 -eq 1 ]
    then
        echo 1
    else 
        local temp=$[ $1 - 1 ]
        local result=$(factorial $temp)
        echo $[ $result * $1 ]
    fi
}

read -p "Enter value: " value
result=$(factorial $value)
echo "The factorial of $value is: $result"

zhangwenming@ubuntu61-PowerEdge-R730:~/workspace/Tomorrow$ ./test.sh
Enter value: 5
The factorial of 5 is: 120
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354