使用多个命令
运行多个命令,可以把它们放在同一行彼此之间用分号隔开。
date;who
创建shell脚本文件
第一行 #!/bin/bash
在shell脚本中,(# )用作注释行,#后的!会告诉shell用哪个shell来运行脚本
让shell找到脚本的方法:用绝对或相对文件路径来引用shell脚本;将shell脚本文件所在的目录添加到PATH环境变量中。./test1
修改脚本可执行权限:chmod +x test1
显示消息
echo命令
echo string,默认情况下不需要使用双引号将要显示的文本字符串划定出来
echo This is a test
如果字符串出现引号:
echo "Let's see if this'll work"
echo 'Rich says "scripting is easy".'
如果想把文本字符串和命令输在同一行,使用echo -n
echo -n "The time and date are: "
date
环境变量
显示一份完整的当前环境变量列表
set
使用环境变量:$variable
echo "User info for userid: $USER"
echo UID:$UID
echo HOME:$HOME
在脚本显示美元符号:$
用户变量
用户变量名区分大小写
创造变量:变量名=变量值 (变量、等号和值之间不能出现空格)
使用变量:$变量名
days=10
guest="keiji"
echo "$guest checked in $days days ago"
value1=100
#将value1的值赋给value2
value2=$value1
命令替换
两种方法:
`command`
$(command)
例子:
testing=`date`
testing=$(date)
echo "The date and time are: " $testing
写一个脚本,用过命令替换获得当前日期并用它生成唯一的文件名
today=$(date +%y%m%d)
touch log.$today
输出重定向>
将命令的输出发送到一个文件中:
command > outfile
重定向操作符创建一个test6,并将date命令的输出重定向到该文件中;如果该文件已经存在,>会先把文件清空在输入命令内容
date > test6
如果想将命令的输出追加到已有文件中:>>
who >> test6
输入重定向<
将文件的内容重定向到命令
command < inputfile
记忆方法:在命令行上,命令在左侧文件在右侧,重定向符号“指向”数据流动的方向。
wc < test6
2 11 60
#三个值分别是文本的行数、单词数、字节数
内联输入重定向:<<
你必须指定一个文本标记来划分输入数据的开头和结尾,文本标记必须一致。
format:
command << marker
data
marker
example:
wc << EOF
test string 1
test string 2
test string 3
EOF
3 9 42
管道:|
将一个命令的输出作为另一个命令的输入。Linux会同时运行两个命令,在系统内部将它们连接起来,在第一个命令产生输出的同时,输出会立即送到第二个命令。
command1 | command2
将命令产生的大量输出通过pipe传送到more,可以强制输出在一屏数据显示后停下来
ls -l | more
执行数学运算
expr命令
expr 1 + 5
expr操作符 | |
---|---|
arg1 ( | ) arg2 |
arg1 & arg2 | 如果参数非0非NULL,返回arg1;否则返回0 |
arg1 < arg2 | True返回1,False返回0 |
arg1 <= arg2 | True返回1,False返回0 |
arg1 = arg2 | True返回1,False返回0 |
arg1 != arg2 | True返回1,False返回0 |
arg1 >= arg2 | True返回1,False返回0 |
arg1 >arg2 | True返回1,False返回0 |
arg1 + arg2 | 两数算数运算和 |
arg1 - arg2 | 两数的算数运算差 |
arg1 * arg2 | 两数的算数乘积 |
arg1 / arg2 | 两数的算数商 |
arg1 % arg2 | 返回余数 |
string : regexp | 如果 REGEXP 匹配到了 STRING 中的某个模式,返回该模式匹配 |
match string regexp | 如果 REGEXP 匹配到了 STRING 中的某个模式,返回该模式匹配 |
substr string pos length | 返回起始位置为pos,长度为length个字符的子字符串(从1开始计数) |
index string chars | 返回在string找到chars字符的为否,找不到返回0 |
length string | 返回字符串string的数值长度 |
#乘法运算时要使用反斜线来转义
expr 5 \* 2
使用方括号
$[ operation ]
使用方括号不用担心shell会误解乘号或其他符号。
bash shell只支持整数运算
var1=100
var2=50
var3=45
var4=$[$var1 * ($var2 - $var3)]
浮点解决方案:bc
在shell提示符下bc命令即可访问bash计算器。bc进入,quit退出。
在bc里设置计算结果中保留的小数位数
scale=n
在脚本中使用bc:
variable=$(echo "options;expression" | bc)
option设置变量,多个变量用分号分开。
var1=$(echo "scale=4; 3.44 / 5" | bc)
大量运算
variable=$(bc << EOF
options
statements
expressions
EOF
)
var5=$(bc << EOF
scale=4
a1=( $var1 * $car2)
b1=($var3 * $var4)
a1+b1
EOF
)
退出脚本
退出状态码exit status
,退出码是一个0-255的整数值
查看状态码echo $?
status | condition |
---|---|
0 | 命令成功结束 |
1 | 一般性未知错误 |
2 | 不适合的shell命令 |
126 | 命令不可执行 |
127 | 没找到命令 |
128 | 无效的退出参数 |
130 | Ctrl+c终止的命令 |
255 | |
128+x | 与Linux信号x相关的严重错误 |
exit:允许指定状态码
状态码超过255,会转换为指定的数值除以256后得到的余数
使用if-then语句
以下if-then语句会运行if后面的那个命令,如果该命令的退出码是0(该命令执行成功)
位于then部分的命令就会被执行。
格式1:
if command
then
commands
fi
格式2:
if command; then
commands
fi
#一个简单的例子。pwd成功运行退出码为0,then部分的语句会被执行
if pwd
then
echo "It worked"
fi
#另一个例子。if后接的是错误命令,会产生一个非0的退出码,跳过then部分的语句
if IamNotaCommand
then
echo "It worked"
fi
在then部分,你可以使用多条命令。
testuser="keiji"
if grep $testuser /etc/passwd
then
echo "This is my first command"
echo "This is my second command"
ls -a /home/$testuser/.b* #列出该用户HOME目录的bash文件
fi
if-then-else语句
当if语句的命令返回退出码0时,then部分的命令会被执行;当命令返回非零退出状态码时,会执行else部分的命令。
if command
then
commands
else
commands
fi
完善搜寻用户的例子,用户存在列出其目录下的bash文件;否则告知该用户不存在
testuser="keiji"
if grep $testuser /etc/passwd
then
echo "The bash file for user $testuser are:"
ls -a /HOME/$testuser/.b*
else
echo "The user $testuser does not exist on this system"
fi
嵌套if
要检查/etc/passwd文件是否存在某个用户名以及该用户的目录是否尚在
testuser="keiji"
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"
fi
fi
可以使用另一种形式:elif
if command1
then
commands
elif command2
then
commands
fi
改写上一个例子
testuser="keiji"
if grep $testuser /etc/passwd
then
echo "The user $testuser exists on this system "
elif ls -d /home/$testuser
then
echo "The user $testuser does not exist on this system"
echo "However,$testuser has a directory"
else:
echo "The user $testuser does not exist on this system"
echo "And,$testuser does not have a directory"
fi
test命令:测试退出状态码之外的条件
test condition
if test condition
then
commands
fi
用test命令确定变量中是否有内容
my_variable="full" / my_variable=""
if test $my_variable
then
echo "The $my_variable expression returns a True"
else
echo "The $my_variable expression returns a False"
fi
另一种条件测试方法
[ condition]
if [conditon]
then
commands
fi
数值比较
comparison | description |
---|---|
n1 -eq n2 | 检查n1是否与n2相等 |
n1 -gt n2 | 检查n1是否大于n2 |
n1 -ge n2 | 检查n1是否大于或等于n2 |
n1 -lt n2 | 检查n1是否小于n2 |
n1 -le n2 | 检查n1是否小于或等于n2 |
n1 -ne n2 | 检查n1是否不等于n2 |
脚本一检查value1是否大于5,脚本2检查value1和value2是否相等
if [ $value1 -gt 5 ]
if [ $value1 -eq $value2 ]
字符串比较
comparison | description |
---|---|
str1 = str2 | 检查str1是否和str2相等 |
str1 != str2 | 检查str1是否和str2不同 |
str1 > str2 | 检查str1是都比str2大 |
str1 < str2 | 检查str1是否比str2小 |
-z str | 检查str长度是否为0 |
-n str | 检查str长度是否非0 |
(1)字符串相等性
if [$USER = $testuser ]
if [ $USER != $testuser ]
(2)字符串顺序
大于号和小于号必须转义,否则shell会把它们当做重定向符号。
大于和小于顺序与sort命令所采用的不同。比较测试中使用的是标准的ASCII顺序
if [ $var1 \> $var2 ]
if [$var1 \< $var2 ]
文件比较
comparison | description |
---|---|
-d file | 检查file是否存在并是一个目录 |
-e file | 检查file是否存在 |
-f file | 检查file是否存在并是一个文件 |
-r file | 检查file是否存在并可读 |
-w file | 检查file是否存在并可写 |
-x file | 检查file是否存在并可执行 |
-s file | 检查file是否存在并非空 |
-O file | 检查file是否存在并属于当前用户 |
-G file | 检查file是否存在并属于当前用户群组 |
file1 -nt file2 | file1是否比file2新 |
file1 -ot file2 | file1是否比file2旧 |
(1)检查目录
检查目录是否存在于系统中,适用于准备切换到某个目录或将文件写入目录中
if [ -d $jump_directory ]
then
echo "The $jump_directory exists"
cd $jump_directory
ls
else
echo "The $jump_directory does not exist"
fi
(2)检查对象
location=$HOME
file_name="sentinel"
if [ -e $location ]
then
echo "Ok on the $location directory"
echo "Now checking on the file,$file_name"
if [ -e /$location/$file_name ]
then
echo "Ok on the filename"
echo "Updating Current date..."
date >> $location/$file_name
else
echo "File does not exist"
echo "Nothing to update"
fi
else
echo "The $location directory does not exist"
echo "Nothing to update"
fi
(3)检查文件
item_name=$HOME
if [ -e $item_name ]
then
echo "The item,$item_name does exist"
echo "But is it a file?"
if [ -f $item_name ]
then
echo "Yes,$item_name is a file"
else
echo "No,$item_name is not a file"
fi
else
echo "The item,$item_name does not exist"
fi
(4)检查是否可读
先用-f检查是否为文件,在用-r检查是否可读
(5)检查空文件
非空文件不删除,空文件删除
if [ -f $file_name ]
then
if [ -s $file_name ]
then
echo "The $file_name file exits and has data"
echo "Will not remove this file"
else
echo "The $file_name file exists,but it is empty"
rm $file_name
fi
else
echo "The $file_name file does not exist"
fi
(6)检查是否可写
if [ -w $item_file ]
(7)检查文件是否可以执行
if [ -x test6.txt ]
(8)检查所属关系、默认属组关系
if [ -O $file_name ]
if [ -G $file_name ]
复合条件测试
第一种布尔运算使用AND来组合两个条件
第二种布尔运算使用OR来组合两个条件
[ condition1] && [condition2]
[ conditoin1] || [ condition2 ]
检查用户的$home目录是否存在,检查其中是否有可写入的testing文件
if [ -d $HOME ] && [ -w $HOME/testing]
使用双括号:数学表达式
shell允许你在条件中使用高级数学表达式
(( expression ))
signal | description |
---|---|
val++ | 后增 |
val– | 后减 |
++val | 先增 |
–val | 先减 |
! | 逻辑求反 |
~ | 位求反 |
** | 幂运算 |
<< | 左位移 |
>> | 右位移 |
& | 位布尔和 |
( | ) |
&& | 逻辑和 |
( |
if (( $val1 ** 2 > 90 ))
使用双方括号
双方括号提供了模式匹配(pattern matching)
[[ expression ]]
if [[ $USER == r* ]]
case命令
case $variable in
pattern1 | pattern2)
commands;;
pattern3)
commands;;
*)
commands;;
esac
for命令
重复执行一系列命令
for var in list
do
commands
done
读取列表中的值
for test in Alabama Alaska Arizona Arkansas
do
echo The next state is $test
done
echo "The last atate we visited was $test" #变量会一直保持最后一次迭代的值,除非你修改它
读取列表中的复杂值
两种办法:使用转义符来将单引号转义
使用双引号来定义用单引号的值
for test in I don\'t know if "this'll" work
do
echo "word:$test"
done
对于包含空格的数值,必须用双引号将他们圈起来。因为for命令用空格来划分列表中的每个值。
for test in Nevada "New Hampshire" "New Mexico" "New York"
do
echo "Now going to $test"
done
从变量读取列表
#将一个列表放在变量里
list="Alabama Alaska Arizona Arkansas Colorado"
#向$list变量包含的已有列表中添加一个值
list=$list" Connecticut"
for state in $list
do
echo "Now go to $state"
done
从命令读取值
for state in $(cat $file)
更改字段的分割符
IFS(internal field separator):内部字段分隔符
默认情况下,shell会将 空格、制表符和换行符当做字段分隔符。这在处理含有空格的数据时,非常麻烦。
想修改IFS的值使其只能识别换行符,忽略空格和制表符:
IFS=$'\n'
修改IFS值之前要保存原来的IFS值
IFS_OLD=$IFS
IFS=$'\n'
IFS=$IFS_OLD
将IFS的值设为冒号
IFS=:
指定多个IFS字符
IFS=$'\n':;"
用通配符读取目录
循环处理某目录下的所有文件
for file in /home/rich/test*
因为文件名之间可能有空格,在判断文件类型时需要把文件名用双引号圈起来
if [ -f "$file" ]
C语言风格的for命令
for (( variable assignment; condition; iteration process ))
变量赋值可以用空格
条件中的变量不以$开头
迭代过程的算式未用expr命令格式
使用多个变量
for (( a=1,b=10; a<=10;a++,b--))
do
echo "$a-$b"
done
while命令
只要测试条件成立,命令就会不停地循环
while [condition]
do
commands
done
打印10-0
var1=10
while [ $var1 -gt 0 ]
do
echo $var1
var1=$[ $var1 -1 ]
done
使用多个测试命令
在while语句定义多个测试命令,只有最后一个测试命令的退出状态码能决定何时停止循环
until命令
只有测试条件成立时,循坏才会结束
until [ condition ]
do
commands
done
嵌套循环
for (( a=1;a<=2;a++ ))
do
echo "Starting loop $a:"
for (( b=1;b<=3;b++ ))
do
echo "Inside loop:$b"
done
done
var1=5
while [ $var1 -ge 0]
do
echo "Outside loop:$var1"
for (( var2=1;var2<3;var2++))
do
var3=$[ $var1*$var2]
echo" Inner loop:$var1 * $var2 = $var3 "
done
var1=$[ $var1 -1 ]
done
循环处理文件数据
IFS_OLD=$IFS
$IFS=$'\n'
for entry in $(cat /etc/passwd)
do
echo "Values in $entry"
$IFS=:
for field in $entry:
do
echo $field
done
done
控制循环:break命令
(1)跳出单个循环
for var1 in 1 2 3 4 5 6 7 8 9 10
do
if [ $var1 -eq 5 ]
then
break #var1等于5就跳出该循环
fi
echo "Iteration number:$var1"
done
while [ $var1 -lt 10 ]
do
if [ $var1 -eq 5 ]
then
break
fi
echo "Itreration:$var1"
var1=$[ $var1 +1 ]
done
(2)跳出内部循环
处理多个循环时,break命令会自动终止你所在的最内层的循环
for ((a=1;a<4;a++))
do
echo "Outer loop:$a"
for ((b=1;b<100;b++)) #跳出这个循环
do
if [ $b -eq 5 ]
then
break
fi
echo " Inner loop:$b"
done
done
(3)跳出外部循环
n指定要跳出的循环层数,默认n=1;n=1表明要跳出当前循环,n=2停止下一级的外部循环
break n
Continue命令
continue会跳过剩余的命令,但不会跳出整个循环。
for (( var1=1 ; var1 < 15; var1++))
do
if [ $var1 -gt 5 ] && [ $var1 -lt 10 ]
then
continue
fi
echo "Iteration number:$var1"
done
处理循环的输出
将for命令的结果重定向到文件,而不是显示在屏幕上
done > output.txt
改变输出结果的顺序done | sort
查找可执行文件
IFS=:
for folder in $PATH
do
echo "$folder:"
for file in $folder/*
do
if [ -x $file ]
then
echo " $file"
fi
done
done
创建多个用户账号
有一个文本文件的格式是:userid,user name
读取该文件各行
while IFS=',' read -r userid name
input="users.csv"
while IFS=',' read -r userid name
do
echo "adding $userid"
useradd -c "$name" -m $usrid
done < "$input"
处理用户输入
命令行参数:
向脚本addem 传递两个命令参数10和30
./addem 10 30
读取参数
$0是程序名,$1是第一个参数以此类推
factorial=1
for (( number=1 ; number<=$1 ; number++))
do
factorial=$[ $factorial * $number]
done
echo "The factorial of $1 is $factotial"
运行
./test1.sh 5
如果输入多个命令行参数,要用空格隔开;包含空格的参数要用引号圈起来
total=$[ $1 *$2 ]
echo "The first parameter is $1"
echo "The second parameter is $2"
echo The total value is $total
运行
./test2.sh 2 5
echo Hello $1,glad to meet you
运行
./test3.sh "Rich Blum"
如果脚本需要的命令行参数不止9个,在第9个后的变量,要在数字周围加上花括号
${10}
读取脚本名
用$0参数获取shell在命令行启动的脚本名
name=$( basename $0)
echo The script name is $name
执行不同功能的脚本,脚本名是addem就将两个参数相加;脚本名是multem就将两个脚本相乘
name=$[ basename $0 ]
if [ $name = "addem" ]
then
total=$[ $1 + $2 ]
elif [ $name = "multem" ]
then
total=$[ $1 * $2 ]
fi
echo "The calculated value is $total"
测试参数
检查是否存在参数
if [ -n "$1" ]
特殊参数变量
参数统计$#
判断参数总数是否为2,是将两个参数相加,不是提示用户
if [ $# -ne 2 ]
then
echo
echo Useage:test9.sh a b
echo
else
total=$[ $1 +$2 ]
echo The total is $total
fi
最后一个参数变量${!#}
当命令行没有参数时,$#的值为0,但 ${ $#}变量会返回脚本名
抓取所有的数据
"$*" 会将命令行所有的参数当做一个单词
"$@" 会将命令行上提供的所有参数当做同一个字符串上的多个独立单词 (可以用for循环逐个获取参数)
移动变量:shift
shift:默认将每个参数向左移动一个位置,最后$1的值会被删除
在不知道参数数量情况下,遍历参数
count=1
while [ -n "$1"]
do
echo "Parameter #$count = $1 "
count=$[ $count +1 ]
shift
done
可以用shift n
来指明要移动的位置数。
获得用户输入:read
read -p 允许直接在read命令行指定提示符
read -p "Please enter your age:" age
days=$[ $age * 365 ]
echo "That makes you over $days days old"
read -p 可以指定多个变量:
read -p "Enter your name:" first last
echo "Checking data for $last $first"
read -t可以指定命令等待的秒数,如果计数器过期,read命令会返回非0退出码
read -t 5 -p "Please enter your name" name
read -n 统计输入的字符数,当输入的字符达到预设的字符数就会自动退出
read -n 1 -p "Do you want to continue [Y/N]?" answer
read -s 隐藏方式读取,避免read命令输入的数据出现在显示器上
read -s -p "Enter your passwd" pass
从文件中读取,用read命令读取文本,每次它都会从文件中读取每一行文本。当文件中再没有内容。
count=1
cat test | while read line
do
echo "Line $count: $line"
count=$[ $count +1 ]
done
echo "Finished processing the file"
理解输入和输出
在显示器屏幕上显示输出
将输出重定向到文件上
标准文件描述符
文件描述符、缩写 | 描述 |
---|---|
0、STDIN | 标准输入 |
1、STDOUT | 标准输出 |
2、STDERR | 标准错误 |
STDIN:文件描述符shell的标准输入。对终端界面来说,标准输入是键盘。shell从STDIN文件标准符对应的键盘获得输入,在用户输入时处理每个字符。输入重定向符号(<)时,Linux会用重定向指定的文件。
cat<marker
data
marker
cat<<EOF>file
data
EOF
STDOUT:代表shell的标准输出。
命令的结果输出到test2文件
ls -l > test2
(>>)将数据追加到某个文件
who >> test2
STDERR:shell文件描述符来处理错误消息
(1)只重定向错误2>file
错误消息不会出现在屏幕上了。该命令生成的任何错误消息都会保存在输出文件中。
ls -al badfile 2>test4
(2)重定向错误和数据
必须用两个重定向符号,正确输出重定向到test7;错误消息重定向到test6
ls -al test test2 test3 badtest 2>test6 1>test7
(3)将STDERR和STDOUT的输出重定向到一个输出文件 &>
ls -al test test2 test3 badtest &> test7
临时重定向
有意在脚本中生成错误信息:>&2
echo "This is an error message" >&2
如何运行脚本使临时重定向生效?错误信息会存到test9中
./test8 2> test9
永久重定向
exec命令:exec 1>testout
exec 2>testerr
告诉shell在脚本执行期间重定向某个特定文件描述符
在脚本中重定向输入
exec 0 < filename
exec命令可以将STDIN重定向到Linux系统上的文件
exec 0< testfile
count=1
while read line
do
echo "Line #$count:$line"
count=$[ $count +1 ]
done
创建输出文件描述符
exec 3>filename
echo string >& 3
exec 3>>filename #输出追加到现有的文件中