《linux命令行与shell脚本编程大全》第三版 - 核心笔记(2/4):构建与结构化脚本

时间:2022-09-10 04:57:45
《linux命令行与shell脚本编程大全》
全书4部分:
☆ 【1】linux命令行(1-10章)
☆ 【2】shell脚本编程基础(11-16章)上:构建与结构化脚本
☆ 【3】高级shell脚本编程(17-23章)
☆ 【4】创建实用的脚本(24-26章)

>>第11章丶构建基本脚本


在创建shell脚本文件时,必须在文件的第一行指定要使用的shell。其格式为:
    #!/bin/bash
    #!/bin/sh            # 目前所在公司使用的sh shell编写脚本,取变量值(没有括号),使用 $VARABLE
井号(#)用作注释行。 shell并不会处理shell脚本中的注释行。然而,shell脚本文件的第一行是个例外, #后面的惊叹号会告诉shell用哪个shell来运行脚本(是的,你
可以使用bash shell,同时还可以使用另一个shell来运行你的脚本)。
/* test1 第一个运行的脚本 */
#!/bin/bash
# This script displays the date and who's logged on
echo The time and date are:
date
echo "Let's see who's logged into the system:"
who
~[shell]$:' ./test1
bash: ./test1: 权限不够
~[shell]$:' ls -l test1
-rw-rw-r-- 1 jiangyuan jiangyuan 55 Jun 11 13:53 test1
~[shell]$:' chmod u+x test1
// 每一个脚本文件执行前都需要 +x 权限,因为ubuntu默认的umask值是022,执行权限必须手动添加
~[shell]$:' ls -l test1
-rwxrw-r-- 1 jiangyuan jiangyuan 55 Jun 11 13:53 test1
~[shell]$:' ./test1
Sun Jun 11 13:54:56 CST 2017
jiangyuan pts/0        2017-06-10 19:41 (:0)
jiangyuan pts/1        2017-06-10 23:48 (:0)
test1

显示消息:
~[shell]$:' echo This is a test.
This is a test.
~[shell]$: echo Let's see if this'll work
Lets see if thisll work
~[shell]$: echo "Let's see if this'll work"
Let's see if this'll work
// 单引号或双引号可以用来划定文本字符串,保证字符串内的引号正常输出。

echo -n "string" // string把文本字符串和命令输出显示在同一行

环境变量:
env 显示所有的当前环境变量列表
/* test2 显示环境变量 */
#!/bin/bash
# This script displays the date and who's logged on
echo "User info for userid: $USER"
echo UID:$UID
echo HOME:$HOME
echo "The cost of the item is \$15" #保证能够准确输出美元符号$15
~[shell]$:' test2
User info for userid: jiangyuan
UID:1001
HOME:/home/jiangyuan
The cost of the item is $15
/* test3 设置用户变量 */
#!/bin/bash
# testing variables
days=10
guest="Katie"
echo "$guest checked in $days days ago"
days=5
guest="Jessica"
echo "$guest checked in $days days ago"
~[shell]$:' test3
Katie checked in 10 days ago
Jessica checked in 5 days ago

[ 注意]引用一个变量值时需要使用美元符,而引用变量来对其进行赋值时则不要使用美元符。
/* test4 引用变量并赋值 */
#!/bin/bash
# assigning a variable value to another variable
value1=10
value2=$value1
echo The resulting value is $value2
~[shell]$:' test4
The resulting value is 10

命令替换:
shell脚本中最有用的特性之一就是可以从命令输出中提取信息,并将其赋给变量。把输出赋给变量之后,就可以随意在脚本中使用了。
有两种方法可以将命令输出赋给变量:
 反引号字符(`) // 和波浪线(~)位于同一键位
 $()格式

/* test5 命令替换 */
#!/bin/bash
# copy the /usr/bin directory listing to a log file
testing=$(date)
echo "The date and time are:"$testing
today=$(date +%Y-%m-%d)
ls /usr/bin -al > log.$today
~[shell]$:' test5
The date and time are:Sun Jun 11 14:23:20 CST 2017
~[shell]$:' ls
log.2017-06-11  test1  test2  test3  test4  test5

命令替换会创建一个子shell来运行对应的命令。子shell(subshell)是由运行该脚本的shell所创建出来的一个独立的子shell(child shell)。正因如此,由该子shell所执行命令是无法使用脚本中所创建的变量的。
在命令行提示符下使用路径./运行命令的话,也会创建出子shell;要是运行命令的时候不加入路径,就不会创建子shell。如果你使用的是内建的shell命令,并不会涉及子shell。在命令行提示符下运行脚本时一定要留心!

重定向输入和输出:
>   将命令的输出发送到一个文件
>>  将命令的输出追加到已有文件
<   将文件的内容重定向到命令 // 在命令行上,命令总是在左侧,而重定向符号“指向”数据流动的方向。小于号说明数据正在从输入文件流向命令。
<<  内联输入重定向,指定输入数据的开始和结尾
    // wc 命令,统计指定文件中的行数、字数、字节数,并将统计结果显示输出。
|   管道被放在命令之间,将一个命令的输出重定向到另一个命令中:command1 | command2
    // 管道最流行的用法之一是将命令产生的大量输出通过管道传送给more命令。

~[shell]$:' date > test6
~[shell]$:' cat test6
Sun Jun 11 14:28:35 CST 2017
~[shell]$:' who >> test6
~[shell]$:' cat test6
Sun Jun 11 14:28:35 CST 2017
jiangyuan pts/0        2017-06-10 19:41 (:0)
jiangyuan pts/1        2017-06-10 23:48 (:0)
~[shell]$:' wc < test6
  3  16 119
~[shell]$:' wc << EOF
> test string 1
> test string 2
> test string 3
> EOF
 3  9 42

执行数学运算命令
expr
命令
允许在命令行上处理数学表达式
~[shell]$:' expr 1 + 5
6
~[shell]$:' expr 5 * 2
expr: 语法错误
~[shell]$:' expr 5 \* 2
10
/* test6 expr命令 */
#!/bin/bash
# An example of using the expr command
var1=10
var2=20
var3=$(expr $var2 / $var1)
var4=$(expr $var1 / $var2)
echo 20/10 The result is $var3
echo 10/20 The result is $var4
~[shell]$: test6
20/10 The result is 2
10/20 The result is 0

在bash中,在将一个数学运算结果赋给某个变量时,可以用美元符和方括号( $[ operation ])将数学表达式围起来。
/* test7 美元符合方括号应用于脚本计算 */
#!/bin/bash
var1=100
var2=50
var3=45
var4=$[$var1 * ($var2 - $var3)]
echo The final result is $var4.
~[shell]$:' test7
The final result is 500.

bash shell数学运算符只支持整数运算。
z shell(zsh)提供了完整的浮点数算术操作。
内建的bash计算器,叫作 bc,能够克服bash中数学运算的整数限制。
浮点运算是由内建变量 scale控制的。
~[shell]$:' bc -q
3.44 / 5
0
scale=4
3.44 / 5
.6880
quit

bash计算器能够支持变量计算:
~[shell]$: bc -q
var1=10
var1*4
40
var2 = var1 / 5
print var2
2
quit

bc

在脚本中使用bc命令,可以用命令替换运行bc命令,并将输出赋给一个变量。基本格式如下:
    variable=$(echo "options; expression" | bc)
    @options 允许设置变量,多个变量;隔开
    @expression 通过bc执行的数学表达式
/* test9 通过bc计算器的浮点运算脚本 */
#!/bin/bash
var1=$(echo "scale=4; 3.44 / 5" | bc)
var2=$(echo "scale=4; 10.2 / 5" | bc)
echo the answer is $var1.
echo the answer is $var2.
~[shell]$:' test9
the answer is .6880.
the answer is 2.0400.
/* test10 使用变量作为表达式中的值 */
#!/bin/bash
var1=100
var2=45
var3=$(echo "scale=4; $var1 / $var2" | bc)
echo The answer for this is $var3.
~[shell]$:' test10
The answer for this is 2.2222.
/* test11 变量被赋值后,也可以再次用于其他运算 */
#!/bin/bash
var1=20
var2=3.14159
var3=$(echo "scale=4; $var1 * $var1" | bc)
var4=$(echo "scale=4; $var3 * $var2" | bc)
echo The final result is $var4
~[shell]$:' test11
The final result is 1256.63600

大量的运算,使用内联输入重定向,它允许你直接在命令行中重定向数据。在shell脚本中,你可以将输出赋给一个变量。
    variable=$(bc << EOF
    options
    statements
    expressions
    EOF
    )

EOF文本字符串标识了内联重定向数据的起止。记住,仍然需要命令替换符号将bc命令的输出赋给变量。
/* test12 多运算的内链输入重定向使用bc计算器 */
#!/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 The final answer for this mess is $var5.
~[shell]$:' test12
The final answer for this mess is 2813.9882.
[ 注意]在bash计算器中创建的变量只在bash计算器中有效,不能在shell脚本中使用。

查看脚本退出状态码:
    $:' date +%Y-%m-%d
    2017-06-11
    $:' echo $?
    0
    // 按照惯例,一个成功结束的命令的退出状态码是0。如果一个命令结束时有错误,退出状态码就是一个正数值。
    $:' asdfg
    -bash: asdfg: command not found
    $:' echo $?
    127

Linux退出状态码:
0       命令成功结束
1       一般性未知错误,如提供了无效参数
2       不适合的shell命令
126     命令不可执行,即没有执行权限
127     没找到命令
128     无效的退出参数
128+x   与Linux信号x相关的严重错误
130     通过Ctrl+C终止的命令
255     正常范围之外的退出状态码

/* 示例:1和126 */
~[shell]$: date %t
date: 无效的日期"%t"
~[shell]$: echo $?
1
~[shell]$: touch myprog.c
~[shell]$: myprog.c
bash: ./myprog.c: 权限不够
~[shell]$: echo $?
126

默认情况下, shell脚本会以脚本中的最后一个命令的退出状态码退出。你可以改变这种默认行为,返回自己的退出状态码。 exit命令允许你在脚本结束时指定一个退出状态码。
/* test13 指定退出状态码 */
#!/bin/bash
# testing the exit status.
var1=10
var2=20
var3=$[$var1 + $var2]
echo The answer is $var3.
exit 5
~[shell]$:' test13
The answer is 30.
~[shell]$:' echo $?
5

[ 注意]也可以返回变量的值作为退出状态码,不过退出状态码范围是0~255之间,超过后,便以取余256的结果显示为最终的退出状态码。

>>第12章丶使用结构化命令


对shell脚本中的命令施加逻辑流程控制,根据条件使脚本跳过某些命令,这样的命令通常称为 结构化命令(structured command)。


if-then


最基本的结构化命令就是if-then语句。 if-then语句有如下格式:
if command1
then
    commands
fi

如果command1的退出状态码为0,该命令成功运行,then部分的commands才会被执行。如果command1退出状态码位其他值,then后面的commands就不会被执行。
/* test1.sh 演示if-then语句的command1退出状态码位0 */
#!/bin/bash
# testing the if statement
if pwd
then
echo "It(pwd) worked."
fi
~[shell]$:' test1.sh
/home/jiangyuan/shell
It(pwd) worked.
/* test2.sh 演示if-then语句的command1退出状态码非0 */
#!/bin/bash
# testing a bad command
if IamNotaCommand
then
echo "It worked"
fi
echo "We are outside the if statement"
~[shell]$:' test2.sh
./test2.sh: 行 3: IamNotaCommand: 未找到命令
We are outside the if statement

if-then语句的另一种形式:
if command; then
    commands
fi

实际效果与之一样,只是类似其他编程语言中的if-then语句。
/* test3.sh 演示if-then语句中多命令的情况 */
#!/bin/bash
# testing multiple commands in the then section
#
testuser=jiangyuan
#
if grep $testuser /etc/passwd; then
echo "This is my first command."
echo "This is my second command."
echo "I can even put in other commands besides echo:"
ls -a /home/$testuser/.b*
fi
~[shell]$:' test3.sh
jiangyuan:x:1001:1001:jiangyuan,,,:/home/jiangyuan:/bin/bash
This is my first command.
This is my second command.
I can even put in other commands besides echo:
/home/jiangyuan/.bash_history  /home/jiangyuan/.bashrc
/home/jiangyuan/.bash_logout
// 如果该用户不存在,则该脚本什么也不会输出

if-then-else语句,格式如下:
if command
then
    commands1
else
    commands2
fi

当command的退出状态码为0时,commands1会执行;当command的退出状态码为非0时,commands2会执行。
/* test4.sh 演示if-then-else语句基本用法 */
#!/bin/bash
# testing the else section
#
testuser=NoSuchUser
#
if grep $testuser /etc/passwd
then
echo "The bash files for user $testuser are:"
ls -a /home/$testuser/.b*
else
echo "The user $testuser does not exist on this system."
fi
~[shell]$:' test4.sh
The user NoSuchUser does not exist on this system.

if-then可嵌套使用:
/* test5.sh 演示if嵌套 */
#!/bin/bash
# testing nested ifs
#
testuser=NoSuchUser
#
if grep $testuser /etc/passwd
then
echo "The user $testuser exists on this system."
else
echo "The user $testuser does not exist on this system."
if ls -d /home/$testuser
then
echo "However, $testuser has a directory"
else
echo "The user has no directory."
fi
fi
~[shell]$:' test5.sh
The user NoSuchUser does not exist on this system.
ls: 无法访问/home/NoSuchUser: 没有那个文件或目录
The user has no directory.

elif

可以使用else部分的另一种形式: elif。这样就不用再书写多个if-then语句了。 elif使用另一个if-then语句延续else部分,格式如下:
if command1
then
    commands
elif command2
then
    more commands
else
    more commands
fi

如果elif后的command2命令返回退出状态码为0,则bash会执行第二个then语句部分的命令,即more commands
/* test5.sh 演示 elif ,注意第一个if后面就不能有else了,逻辑不通 */
#!/bin/bash
# testing nested ifs
#
testuser=NoSuchUser
#
if grep $testuser /etc/passwd
then
echo "The user $testuser exists on this system."
elif ls -d /home/$testuser
then
echo "However, $testuser has a directory"
else
echo "The user has no directory."
fi
~[shell]$:' test5.sh
ls: 无法访问/home/NoSuchUser: 没有那个文件或目录
The user has no directory.

[ 窍门] 记住,在elif语句中,紧跟其后的else语句属于elif代码块。它们并不属于之前的if-then代码块。

可以继续将多个elif语句串起来,形成一个大的if-then-elif嵌套组合:
if command1
then
    command set 1
elif command2
then
    command set 2
elif command3
then
    command set 3
elif command4
then
    command set 4
else
    more commands
fi

test

提供了在if-then语句中测试不同条件的途径。如果test命令中列出的条件成立,test命令就会退出并返回退出状态码0,格式:
    test condition
    @condition 是test命令要测试的一系列参数和值
与if-then结合起来使用是这样的:
if test condition
then
    commands
fi

/* test6.sh 演示test命令 */
#!/bin/bash
# Testing the test command
#
if test
then
echo "No expression returns a True"
else
echo "No expression returns a False"
fi

~[shell]$:' test6.sh
No expression returns a False
// 如果不写test命令的condition部分,它会以非零的退出状态码退出, 并执行else语句块。
/* test6.sh 演示test命令 */
#!/bin/bash
# Testing the test command
#
my_var="Full"
#
if test $my_var
then
echo "The $my_var expression returns a True."
else
echo "The $my_variable expression returns a false."
fi
~[shell]$:' test6.sh
The Full expression returns a True.
// 如果 my_var="" 变量中没有包含内容,则结果会执行else后的命令。

bash shell提供了另一种条件测试方法,无需在if-then语句中声明test命令,格式如下;
if [ condition ]
then
    commands
fi


[ 注意] [] 方括号定义了测试条件,注意 condition 的前后各有一个空格,否则就会报错!!!!
condition可以判断三种条件:
 数值比较
 字符串比较
 文件比较


数值比较:
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
# Using numeric test evaluations
#
value1=10
value2=11
#
if [ $value1 -gt 5 ]
then
echo "The test value $value1 is greater than 5"
fi
#
if [ $value1 -eq $value2 ]
then
echo "The values are equal"
else
echo "The values are different."
fi
~[shell]$:' numeric_test.sh
The test value 10 is greater than 5
The values are different.

[ 注意] 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
/* test7.sh 字符串比较演示 */
#!/bin/bash
# testing string equality
testuser=jiangyuan
#
if [ $USER = $testuser ]
then
echo "Welcome $testuser"
fi
~[shell]$:' test7.sh
Welcome jiangyuan
/* test8.sh 字符串不等条件中,也可以判断字符串中是否有相同值 */
#!/bin/bash
# testing string equality
#
testuser=baduser
#
if [ $USER != $testuser ]
then
echo "This is not $testuser"
else
echo "Welcome $testuser"
fi
~[shell]$:' test8.sh
This is not baduser
/* test9.sh 字符串顺序的比较,必须对大于或小于号使用转义 */
#!/bin/bash
# mis-using string comparisons
#
val1=baseball
val2=hockey
#
if [ $val1 \> $val2 ]
then
echo "$val1 is greater than $val2"
else
echo "$val1 is less than $val2"
fi
[~/shell]$:' test9.sh
baseball is less than hockey
/* test10.sh 测试检查字符串大小 */
#!/bin/bash
# testing string length
val1=testing
val2=''
#
if [ -n $val1 ]
then
echo "The string '$val1' is not empty"
else
echo "The string '$val1' is empty"
fi
#
if [ -z $val2 ]
then
echo "The string '$val2' is empty"
else
echo "The string '$val2' is not empty"
fi
#
if [ -z $val3 ]
then
echo "The string '$val3' is empty"
else
echo "The string '$val3' is not empty"
fi
[~/shell]$:' test10.sh
The string 'testing' is not empty
The string '' is empty
The string '' is empty

[ 窍门] 空的和未初始化的变量会对shell脚本测试造成灾难性的影响。如果不是很确定一个变量的内容,最好在将其用于数值或字符串比较之前先通过-n或-z来测试一下变量是否含有值。

文件比较:
-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旧

复合条件测试:
if-then语句允许你使用布尔逻辑来组合测试。有两种布尔运算符可用:
 [ condition1 ] && [ condition2 ]
 [ condition1 ] || [ condition2 ]


[ 窍门] 布尔逻辑是一种能够将可能的返回值简化为TRUE或FALSE的方法。
/* 复合条件测试伪代码 */
if [ -d $HOME ] && [ -w $HOME/testing ]
then
echo "The file exists and you can write to it"
else
echo "I cannot write to the file"
fi

if-then语句中使用的高级特性:
 用于数学表达式的双括号,格式:(( expression ))
 用于高级字符串比较处理功能的双方括号,格式:[[ expression ]]


双括号命令符号:
val++   后增
val--   后减
++val   先增
--val   先减
!       逻辑求反
~       位求反
**      幂运算
<<      左位移
>>      右位移
&       位布尔和
|       位布尔或
&&      逻辑和
||      逻辑或
/* 双括号伪代码演示 */
val1=10
#
if (( $val1 ** 2 > 90 ))
then
(( val2 = $val1 ** 2 ))
echo "The square of $val1 is $val2"
fi
/* 双方括号伪代码演示 */if [[ $USER == r* ]]then    echo "Hello $USER"else    echo "Sorry, I do not know you"fi

case

不需要再写出所有的elif语句来不停地检查同一个变量的值了。case命令会采用列表格式来检查单个变量的多个值:
case variable in
pattern1 | pattern2) commands1;;
pattern3) commands2;;
*) default commands;;
esac

/* case命令演示 */
#!/bin/bash
# using the case command
#
case $USER in
jiangyuan | other)
echo "Welcome, $USER"
echo "Please enjoy your visit";;
testing)
echo "Special testing account";;
unknowname)
echo "Do not forget to log off when you're done";;
*)
echo "Sorry, you are not allowed here.";;
esac
[~/shell]$:' test26.sh
Welcome, jiangyuan
Please enjoy your visit

>>第13章丶更多的结构化命令


for

创建一个遍历一系列值的循环。每次迭代都使用其中一个值来执行已定义好的一组命令。
基本格式:
for var in list
do
    commands
done

list是迭代中要用到的一系列值,每次迭代,var会包含list中的值,第一次迭代使用第一个值,第二次迭代使用第二个值,以此类推。
也可以合并前两行: for var in list; do
/* 读取列表中的值 */
#!/bin/bash
# basic for command
for test in Alabama Alaska Arizona Arkansas California Colorado
do
echo The next state is $test.
done
[~/shell]$:' test1
The next state is Alabama.
The next state is Alaska.
The next state is Arizona.
The next state is Arkansas.
The next state is California.
The next state is Colorado.

在最后一次迭代后, $test变量的值会在shell脚本的剩余部分一直保持有效。它会一直保持最后一次迭代的值,修改后则变为修改值。
/* 读取列表中的复杂值 */
#!/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
# 使用转义字符\来讲单引号转义,使用双引号来定义用到单引号的值
[~/shell]$:' test2
word:I
word:don't
word:know
word:if
word:this'll
word:work

如果在单独的数据值中有空格,就必须用双引号将这些值圈起来。
示例:for test in Nevada "New Hampshire" "New Mexico" "New York"

从变量中读取列表中的值:
list="Alabama Alaska Arizona Arkansas Colorado"
list=$list" Connecticut"
for state in $list
do
    echo "Have you ever visited $state?"
done

从命令中读取值:
file="states"
# write into file
for name in Alabama Alaska Arizona Arkansas Colorado Connecticut Delaware Florida Georgia
do
echo $name >> $file
echo "write $name to $file success..."
done
# read out of file
for state in $(cat $file)
do
echo "Visit beautiful $state."
done
[~/shell]$:' test3
write Alabama to states success...
write Alaska to states success...
write Arizona to states success...
write Arkansas to states success...
write Colorado to states success...
write Connecticut to states success...
write Delaware to states success...
write Florida to states success...
write Georgia to states success...
Visit beautiful Alabama.
Visit beautiful Alaska.
Visit beautiful Arizona.
Visit beautiful Arkansas.
Visit beautiful Colorado.
Visit beautiful Connecticut.
Visit beautiful Delaware.
Visit beautiful Florida.
Visit beautiful Georgia.

默认情况下, bash shell会将下列字符当作字段分隔符:
 空格
 制表符 \t
 换行符 \n


IFS

IFS=$'\n'
将这个语句加入到脚本中,告诉bash shell在数据值中忽略空格和制表符,不会忽略换行符。

在处理代码量较大的脚本时,可能在一个地方需要修改IFS的值,然后忽略这次修改,在脚本的其他地方继续沿用IFS的默认值。一个可参考的安全实践是在改变IFS之前保存原来的IFS值,之后再恢复它。这种技术可以这样实现:
    IFS.OLD=$IFS
    IFS=$'\n'
    <在代码中使用新的IFS值>
    IFS=$IFS.OLD

这就保证了在脚本的后续操作中使用的是IFS的默认值。

如果要指定多个IFS字符,只要将它们在赋值行串起来就行。
    IFS=$'\n':;"

用通配符读取目录:
/* 示例通配符,注意$file使用双引号引起来,为了避免含有空格的文件名会出错 */
#!/bin/bash
# iterate through all the files in a directory
for file in /home/jiangyuan/shell/*
#*/
do
if [ -d "$file" ]
then
echo "$file is a directory"
elif [ -f "$file" ]
then
echo "$file is a file"
fi
done
[~/shell]$:' test4
/home/jiangyuan/shell/1 is a directory
/home/jiangyuan/shell/2 is a directory
/home/jiangyuan/shell/states is a file
/home/jiangyuan/shell/test1 is a file
/home/jiangyuan/shell/test2 is a file
/home/jiangyuan/shell/test3 is a file
/home/jiangyuan/shell/test4 is a file

bash中C语言风格的for循环的基本格式:
    for (( variable assignment ; condition ; iteration process ))
C语言风格的for命令看起来如下:
    for (( a = 1; a < 10; a++ ))
注意,有些部分并没有遵循bash shell标准的for命令:
 变量赋值可以有空格;
 条件中的变量不以美元符开头;
 迭代过程的算式未用expr命令格式。

/* C语言风格的for命令脚本 */
#!/bin/bash
# testing the C-style for loop
for (( a=1, b=10; a <= 10; a++, b-- ))
do
echo "a $a --- b $b"
done
[~/shell]$:' test5
a 1 --- b 10
a 2 --- b 9
a 3 --- b 8
a 4 --- b 7
a 5 --- b 6
a 6 --- b 5
a 7 --- b 4
a 8 --- b 3
a 9 --- b 2
a 10 --- b 1

while

while命令允许定义一个要测试的命令,然后循环执行一组命令,只要定义的测试命令返回的是退出状态码0。它会在每次迭代的一开始测试test命令。在test命令返回非零退出状态码时, while命令会停止执行那组命令。while的基本格式:
while test command
do
    other commands
done

/* while循环脚本演示 */
#!/bin/bash
# while command test
var1=10
while echo -n "$var1: "
[ $var1 -ge 0 ]
do
echo "This is inside the loop."
var1=$[ $var1 - 1 ]
done
[~/shell]$: test6
10: This is inside the loop.
9: This is inside the loop.
8: This is inside the loop.
7: This is inside the loop.
6: This is inside the loop.
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: This is inside the loop.
-1:
// 在每次迭代中所有的测试命令都会被执行,包括测试命令失败的最后一次迭代。要留心这种用法。

until

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

/* until命令演示循环 */
#!/bin/bash
# using the until command
var1=100
until echo $var1
[ $var1 -eq 0 ]
do
echo Inside the loop: $var1
var1=$[ $var1 - 25 ]
done
[~/shell]$:' test7
100
Inside the loop: 100
75
Inside the loop: 75
50
Inside the loop: 50
25
Inside the loop: 25
0

嵌套循环
/* 双重for循环 */
#!/bin/bash
# nesting for loops
for (( a = 1; a <= 3; a++ ))
do
echo "Starting loop $a:"
for (( b = 1; b <= 3; b++ ))
do
echo " Inside loop: $b"
done
done
/* while中使用for循环 */#!/bin/bash# placing a for loop inside a while loopvar1=5while [ $var1 -ge 0 ]do    echo "Outer loop: $var1"    for (( var2 = 1; $var2 < 3; var2++ ))    do        var3=$[ $var1 * $var2 ]        echo " Inner loop: $var1 * $var2 = $var3"    done        var1=$[ $var1 - 1 ]done
/* until中使用while循环 */#!/bin/bash# using until and while loopsvar1=3until [ $var1 -eq 0 ]do    echo "Outer loop: $var1"    var2=1    while [ $var2 -lt 5 ]    do        var3=$(echo "scale=4; $var1 / $var2" | bc)        echo " Inner loop: $var1 / $var2 = $var3"        var2=$[ $var2 + 1 ]    done        var1=$[ $var1 - 1 ]done

循环处理文件数据
 使用嵌套循环
 修改IFS环境变量

/* 示例解析 /etc/passwd 文件中的数据 */
#!/bin/bash
# changing the IFS value
IFS.OLD=$IFS
IFS=$'\n'
path=/etc/passwd
for entry in $(cat $path)
do
echo "Values in $entry --"
IFS=:
for value in $entry
do
echo " $value"
done
done
[~/shell]$:' test9
Values in root:x:0:0:root:/root:/bin/bash --
        root
        x
        0
        0
        root
        /root
        /bin/bash
Values in daemon:x:1:1:daemon:/usr/sbin:/bin/sh --
        daemon
        x
        1
        1
        daemon
        /usr/sbin
        /bin/sh

break

退出任意循环,默认值跳出最近的一层循环。
break n
退出任意循环的层级,n为1退出当前循环,n为2则退出下一级的外部循环。
/* 示例跳出指定循环层级 */
#!/bin/bash
# breaking out of an outer loop
for (( a = 1; a < 4; a++ ))
do
echo "Outer loop: $a"
for (( b = 1; b < 100; b++ ))
do
if [ $b -gt 4 ]
then
break 2
fi
echo " Inner loop: $b"
done
done
[~/shell]$:' test10
Outer loop: 1
 Inner loop: 1
 Inner loop: 2
 Inner loop: 3
 Inner loop: 4

continue

提前中止某次循环中的命令,但并不会完全终止整个循环。同C语言的continue,跳过循环的某次(由条件指定)。
continue n
也允许通过命令行参数指定要继续执行哪一级循环。
/* 示例跳过指定循环层级 */
#!/bin/bash
# continuing an outer loop
for (( a = 1; a <= 5; a++ ))
do
echo "Iteration $a:"
for (( b = 1; b < 3; b++ ))
do
if [ $a -gt 2 ] && [ $a -lt 4 ]
then
continue 2
fi
var3=$[ $a * $b ]
echo " The result of $a * $b is $var3"
done
done

处理循环的输出
done > output.txt
done | sort


/* 【实例】查找可执行文件 */
#!/bin/bash
# finding files in the PATH
# 创建一个for循环,对环境变量PATH中的目录进行迭代。处理的时候别忘了设置IFS
分隔符
IFS=:
for folder in $PATH
do
echo "$folder:"
# 已经将各个目录存放在了变量$folder中,可以使用另一个for循环来迭代特定目录
中的所有文件
for file in $folder/*
#*/
do
# 检查各个文件是否具有可执行权限
if [ -x $file ]
then
echo " $file"
fi
done
done

/* 【实例】创建多个用户账户 */
#!/bin/bash
# process new user accounts
input="users.csv"
while IFS=',' read -r userid name
do
echo "adding $userid..."
useradd -c "$name" -m "userid"
done < "$input"
[~/shell]$:' cat users.csv
rich,Richard Blum
christine,Christine Bresnahan
barbara,Barbara Blum
tim,Timothy Bresnahan
// 需要root权限才能运行创建多个用户账户的脚本



2017.06.11
第11章、12章、13章完...(本部分还有14/15/16章)未完待续!