作者:じ☆ve不哭
发布时间:2023-08-21T09:43:18
行首以#(#!是个例外)开头是注释 ,也可以放在于本行命令的后边.
echo "The # here does not begin a comment."
echo 'The # here does not begin a comment.'
echo The \# here does not begin a comment.
echo The # 这里开始一个注释.
echo ${PATH#*:} # 参数替换, 不是一个注释.
echo $(( 2#101011 )) # 数制转换, 不是一个注释.
# 感谢, S.C.
命令分隔符[分号, 即;]. 可以在同一行上写两个或两个以上的命令.
echo hello; echo there
if [ -x "$filename" ]; then # 注意: "if"和"then"需要分隔.
# 为什么?
echo "File $filename exists."; cp $filename $filename.bak
else
echo "File $filename not found."; touch $filename
fi; echo "File test complete."
终止case选项[双分号, 即;;]
case "$variable" in
abc) echo "\$variable = abc" ;;
xyz) echo "\$variable = xyz" ;;
esac
"点"命令[句点, 即.]. 等价于source命令
"点"作为文件名的一部分. 如果点放在文件名的开头的话, 那么这个文件将会成为"隐藏"文件, 并且ls命令将不会正常的显示出这个文件.
touch .hidden
ls
ls -a
如果作为目录名的话, 一个单独的点 代表当前的工作目录, 而 两个点 表示上一级目录.
cd .
cd ..
点 经常会出现在文件移动命令的目的参数(目录)的位置上.
cp /home/bozo/current_work/junk/* .
"点"字符匹配. 当用作匹配字符的作用时, 通常都是作为正则表达式的一部分来使用, "点"用来 匹配任何的单个字符.
部分引用[双引号, 即"]. "STRING" 将会阻止(解释) STRING 中大部分特殊的字符
全引用[单引号, 即']. 'STRING' 将会阻止 STRING 中所有特殊字符的解释. 这是一种比使用"更强 烈的形式.
逗号操作符. 逗号操作符链接了一系列的算术操作. 虽然里边所有的内容都被运行了,但只有最后 一项被返回.
et "t2 = ((a = 9, 15 / 3))" # Set "a = 9" and "t2 = 15 / 3"
转义符[反斜线, 即]. 一种对单字符的引用机制. \X 将会"转义"字符 X . 这等价于 "X" , 也等价于 'X' . \通常用来转义"和', 这样双引号和但引号就 不会被解释成特殊含义了.
文件名路径分隔符[斜线, 即/]. 分隔文件名不同的部分(比如 /home/bozo/projects/Makefile ). 也可以用来作为除法算术操作符.
替换. command
结构可以将命令的输出赋值到一个变量中去. 我们在后边的后置引用
(backquotes)或后置标记(backticks)中也会讲解.
空命令[冒号, 即:]. 等价于"NOP" ( no op , 一个什么也不干的命令). 也可以被认为与shell的 内建命令true作用相同. ":"命令是一个bash的内建命令, 它的退出码(exit status)是"true"(0).
在if/then中的占位符:
if condition
then : # 什么都不做,引出分支.
else
take-some-action
fi
在与>重定向操作符结合使用时, 将会把一个文件清空, 但是并不会修改这个文件的权限. 如果之 前这个文件并不存在, 那么就创建这个文件.
: > data.xxx
# 与 cat /dev/null >data.xxx 的作用相同
# 然而,这并不会产生一个新的进程, 因为":"是一个内建命令.
也可能用来作为注释行, 虽然我们不推荐这么做. 使用#来注释的话, 将关闭剩余行的错误检查, 所以可以在注释行中写任何东西. 然而, 使用:的话将不会这样.
: This is a comment that generates an error, ( if [ $x -eq 3] ).
":"还用来在 /etc/passwd 和$PATH变量中做分隔符.
echo $PATH
/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/sbin:/usr/sbin:/usr/games
取反操作符[叹号, 即!]. !操作符将会反转命令的退出码的结果, (具体参见例子 6-2). 也会反 转测试操作符的意义, 比如修改"等号"( = )为"不等号"( != ). !操作符是Bash的关键字. 在一个不同的上下文中, !也会出现在变量的间接引用中.
通配符. *可以用来做文件名匹配(这个东西有个专有名词叫globbing)的"通配符". 含义是, 可以用来匹配给定目录下的任何文件名.
算术操作符. 在算术操作符的上下文中, 号表示乘法运算. 如果要做求幂运算, 使用*, 这是求幂操作符.
测试操作符. 在一个特定的表达式中, ?用来测试一个条件的结果. 在一个双括号结构中, ?就是C语言的三元操作符. 参见例子 9-31. 在参数替换表达式中, ?用来测试一个变量是否被set了. .
通配符. ?在通配(globbing)中, 用来做匹配单个字符的"通配符", 在正则表达式中, 也是用 来表示一个字符.
变量替换(引用变量的内容).
var1=5
var2=23skidoo
echo $var1 # 5
echo $var2 # 23skidoo
在一个变量前面加上$用来引用这个变量的 值
行结束符. 在正则表达式中, "$"表示行结束符.
参数替换.
位置参数.
退出状态码变量. $? 变量 保存了一个命令, 一个函数, 或者是脚本本身的退出状态码.
进程ID变量. 这个$$ 变量 保存了它所在脚本的 进程 ID
命令组.
(a=hello; echo $a)
a=123
( a=321; )
echo "a = $a" # a = 123
# 在圆括号中a变量, 更像是一个局部变量.
初始化数组.
Array=(element1 element2 element3)
大括号扩展.
cat {file1,file2,file3} > combined_file
# 把file1, file2, file3连接在一起, 并且重定向到combined_file中.
cp file22.{txt,backup}
# 拷贝"file22.txt"到"file22.backup"中
一个命令可能会对 大括号 [2] 中的以逗号分割的文件列表起作用. (通配(globbing))将对大括号 中的文件名做扩展. 在大括号中, 不允许有空白, 除非 这个空白被引用或转义.
echo {file1,file2}\ :{\ A," B",' C'}
#file1 : A file1 : B file1 : C file2 : A file2 : B file2 : C
{ 代码块. 又称内部组 , 这个结构事实上创建了一个 匿名函数. 然而, 与"标准"函数不同的是, 在其中声明的变量,对于脚本其他部分的代码来说还是可见的.}
a=123
{ a=321; }
echo "a = $a" # a = 321 (说明在代码块中对变量a所作的修改, 影响了外边的变量)
代码块和I/O重定向
#!/bin/bash
# 从/etc/fstab中读行.
File=/etc/fstab
{
read line1
read line2
} < $File
echo "First line in $File is:"
echo "$line1"
echo
echo "Second line in $File is:"
echo "$line2"
exit 0
路径名. 一般都在find命令中使用. 这 不是 一个shell内建命令.
条件测试. 条件测试表达式放在[ ]中. 值得注意的是[是shell内建test命令的一部分, 并不 是 /usr/bin/test 中的外部命令的一个链接.
数组元素. 在一个array结构的上下文中, 中括号用来引用数组中每个元素的编号.
Array[1]=slot_1
echo ${Array[1]}
字符范围.用作正则表达式的一部分, 方括号描述一个匹配的字符范围.
测试. 测试表达式放在[[ ]]中
整数扩展. 扩展并计算在(( ))中的整数表达式.
重定向. scriptname >filename 重定向 scriptname 的输出到文件 filename 中. 如果 filename 存在的话, 那 么将会被覆盖. command &>filename 重定向 command 的 stdout 和 stderr 到 filename 中. command >&2 重定向 command 的 stdout 到 stderr 中. scriptname >>filename 把 scriptname 的输出追加到文件 filename 中. 如果 filename 不存在的话, 将会被创建. [i]<>filename 打开文件 filename 用来读写, 并且分配文件描述符i给这个文件. 如果 filename 不 存在, 这个文件将会被创建. 进程替换. (command)> <(command) 在一种不同的上下文中, "<"和">"可用来做 字符串比较操作. 在另一种上下文中, "<"和">"可用来做 整数比较操作.
用在here document中的重定向.
用在here string中的重定向.
ASCII comparison.
veg1=carrots
veg2=tomatoes
if [[ "$veg1" < "$veg2" ]]
then
echo "Although $veg1 precede $veg2 in the dictionary,"
echo "this implies nothing about my culinary preferences."
else
echo "What kind of dictionary are you using, anyhow?"
fi
正则表达式中的单词边界 .
grep '\<the\>' textfile
管道. 分析前边命令的输出, 并将输出作为后边命令的输入. 这是一种产生命令链的好方法.
echo ls -l | s
# 传递"echo ls -l"的输出到shell中,
#+ 与一个简单的"ls -l"结果相同.
管道是进程间通讯的一个典型办法, 将一个进程的 stdout 放到另一个进程的 stdin 中. 标 准的方法是将一个一般命令的输出, 比如cat或者echo, 传递到一个 "过滤命令"(在这个 过滤命令中将处理输入)中, 然后得到结果. cat $filename1 $filename2 | grep $search_word
强制重定向(即使设置了noclobber选项 -- 就是-C选项). 这将强制的覆盖一个现存文件.
或-逻辑操作. 在一个条件测试结构中, 如果条件测试结构两边中的 任意一边 结果为true的话, ||操作就会返回0(代表执行成功).
后台运行命令. 一个命令后边跟一个& 表示在后台运行.
#!/bin/bash
# background-loop.sh
for i in 1 2 3 4 5 6 7 8 9 10 # 第一个循环.
do
echo -n "$i "
done & # 在后台运行这个循环.
# 在第2个循环之后, 将在某些时候执行.
echo # 这个'echo'某些时候将不会显示.
for i in 11 12 13 14 15 16 17 18 19 20 # 第二个循环.
do
echo -n "$i "
done
echo # 这个'echo'某些时候将不会显示.
# ======================================================
# 期望的输出应该是:
# 1 2 3 4 5 6 7 8 9 10
# 11 12 13 14 15 16 17 18 19 20
# 然而实际的结果有可能是:
11 12 13 14 15 16 17 18 19 20
# 1 2 3 4 5 6 7 8 9 10 bozo $
# (第2个'echo'没执行, 为什么?)
# 也可能是:
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
# (第1个'echo'没执行, 为什么?)
# 非常少见的执行结果, 也有可能是:
# 11 12 13 1 2 3 4 5 6 7 8 9 10 14 15 16 17 18 19 20
# 前台的循环先于后台的执行.
exit 0
# Nasimuddin Ansari 建议加一句 sleep 1
#+ 在6行和14行的 echo -n "$i" 之后加这句.
#+ 为了真正的乐趣.
与-逻辑操作. 在一个条件测试结构中, 只有在条件测试结构的 两边 结果都为true的时候, &&操作 才会返回0(代表sucess).
选项, 前缀. 在所有的命令内如果想使用选项参数的话,前边都要加上"-".
COMMAND -[Option1][Option2][...] ls -al sort -dfu $filename set -- $variable
if [ $file1 -ot $file2 ]
then
echo "File $file1 is older than $file2."
fi
if [ "$a" -eq "$b" ]
then
echo "$a is equal to $b."
fi
if [ "$c" -eq 24 -a "$d" -eq 47 ]
then
echo "$c equals 24 and $d equals 47."
fi
先前的工作目录. cd -将会回到先前的工作目录. 它使用了$OLDPWD 环境变量
cd -
减号. 减号属于算术操作.
等号. 赋值操作
a=28
echo $a # 28
加号. 加法算术操作. 在另一种上下文环境中, +也是一种正则表达式操作.
选项. 一个命令或者过滤器的选项标记. 某些命令内建命令使用+来打开特定的选项, 用-来禁用这些特定的选项.
取模. 取模(一次除法的余数)算术操作. 在不同的上下文中, %也是一种模式匹配操作.
home目录. 相当于$HOME内部变量. ~leon 是leon的home目录,
并且ls ~leon将列出其中的内容. ~/就是当前用户的home目录, 并且ls ~/将列出其中的内容.
# echo ~leon
/home/leon
当前工作目录. 相当于$PWD内部变量. ~- 先前的工作目录. 相当于$OLDPWD内部变量.
正则表达式匹配. 这个操作将会在version 3版本的Bash部分进行讲解.
行首. 在正则表达式中, "^"表示定位到文本行的行首.
退格(非破坏性的), 就是退格但是不删掉前面的字符.
break. 终结一个前台作业
在console或者在 xterm 窗口中输入的时候, Ctl-D 将删除光标下字符. 当没有字符时, Ctl- D 将退出当前会话, 在一个xterm窗口中, 则会产生关闭此窗口的效果
"哔" (beep). 在一些老式的打字机终端上, 它会响一下铃.
"退格"(破坏性的), 就是在退格之后, 还要删掉前边的字符.
#!/bin/bash
# Embedding Ctl-H in a string.
a="^H^H" # 两个 Ctl-H's (backspaces).
echo "abcdef" # abcdef
echo -n "abcdef$a " # abcd f
# Space at end ^ ^ 两次退格.
echo -n "abcdef$a" # abcdef
# 结尾没有空格 没有 backspace 的效果了(why?).
# 结果并不像期望的那样.
echo; echo
水平制表符.
重起一行(换一行并到行首). 在脚本中, 也可以使用8进制表示法 -- '\012' 或者16进制 表示法 -- '\x0a' 来表示.
垂直制表符. 当在console或者 xterm 窗口中输入文本时, Ctl-K 将会删除从光标所在处到行为的全部字 符. 在脚本中, Ctl-K 的行为有些不同, 具体请参见下边的Lee Maschmeyer的例子程序.
清屏(清除终端的屏幕显示). 在终端中, 与clear命令的效果相同. 当发送到打印机上时, Ctl-L 会让打印机将打印纸卷到最后.
回车.
恢复(XON). 在一个终端中恢复 stdin .
挂起(XOFF). 在一个终端中冻结 stdin . (使用Ctl-Q可以恢复输入.)
删除光标到行首的所有字符. 在某些设置下, 不管光标的所在位置 Ctl-U 都将删除整行输 入.
当输入字符时, Ctl-V 允许插入控制字符. 比如, 下边的两个例子是等价的:
1 echo -e '\x0a'
2 echo
当在控制台或一个xterm窗口敲入文本时, Ctl-W 将会删除当前光标到左边最近一个空格间 的全部字符. 在某些设置下, Ctl-W 将会删除当前光标到左边第一个非字母或数字之间的全 部字符.
暂停前台作业.
用来分隔函数, 命令或变量. . 空白包含 空格 , tab , 空行 , 或者是它们之间任意的组合体. [4] 在某些上下文中, 比如变量赋值, 空白是不被允许的, 会产生语法错误. 空行不会影响脚本的行为, 因此使用空行可以很好的划分独立的函数段以增加可读性. 特殊变量$IFS用来做一些输入命令的分隔符, 默认情况下是空白. 如果想在字符串或变量中使用空白, 那么应该使用引用.
变量的 名字 就是变量保存 值 的地方. 引用变量的值就叫做 变量替换
#!/bin/bash
# 变量赋值和替换
a=375
hello=$a
#------------------------------------------------------------------
# 强烈注意, 在赋值的的时候, 等号前后一定不要有空格.
# 如果出现空格会怎么样?
# "VARIABLE =value"
# ^
#% 脚本将尝试运行一个"VARIABLE"的命令, 带着一个"=value"参数.
# "VARIABLE= value"
# ^
#% 脚本将尝试运行一个"value"的命令,
#+ 并且带着一个被赋值成""的环境变量"VARIABLE".
#------------------------------------------------------------------
-------
echo hello # 没有变量引用, 只是个hello字符串.
echo $hello
echo ${hello} # 同上.
echo "$hello"
echo "${hello}"
echo
hello="A B C D"
echo $hello # A B C D
echo "$hello" # A B C D
# 就象你看到的echo $hello 和 echo "$hello" 将给出不同的结果.
# ===============================================================
# 引用一个变量将保留其中的空白, 当然, 如果是变量替换就不会保留了.
# ===============================================================
echo
echo '$hello' # $hello
# ^ ^
# 全引用的作用将会导致"$"被解释为单独的字符,
#+ 而不是变量前缀.
# 注意这两种引用所产生的不同的效果.
hello= # 设置为空值.
echo "\$hello (null value) = $hello"
# 注意设置一个变量为null, 与unset这个变量, 并不是一回事
#+ 虽然最终的结果相同(具体见下边).
# --------------------------------------------------------------
# 可以在同一行上设置多个变量,
#+ 但是必须以空白进行分隔.
# 慎用, 这么做会降低可读性, 并且不可移植.
var1=21 var2=22 var3=$V3
echo
echo "var1=$var1 var2=$var2 var3=$var3"
# 在老版本的"sh"上可能会引起问题.
# --------------------------------------------------------------
echo; echo
numbers="one two three"
# ^ ^
other_numbers="1 2 3"
# ^ ^
# 如果在变量中存在空白, If there is whitespace embedded within a variable,
#+ 那么就必须加上引用.
# other_numbers=1 2 3 # 给出一个错误消息.
echo "numbers = $numbers"
echo "other_numbers = $other_numbers" # other_numbers = 1 2 3
# 不过也可以采用将空白转义的方法.
mixed_bag=2\ ---\ Whatever
# ^ ^ 在转义符后边的空格(\).
echo "$mixed_bag" # 2 --- Whatever
echo; echo
echo "uninitialized_variable = $uninitialized_variable"
# Uninitialized变量为null(就是没有值).
uninitialized_variable= # 声明, 但是没有初始化这个变量,
#+ 其实和前边设置为空值的作用是一样的.
echo "uninitialized_variable = $uninitialized_variable"
# 还是一个空值.
uninitialized_variable=23 # 赋值.
unset uninitialized_variable # Unset这个变量.
echo "uninitialized_variable = $uninitialized_variable"
# 还是一个空值.
echo
exit 0
= 赋值操作( 前后都不能有空白 )
#!/bin/bash
# "裸体"变量
echo
# 变量什么时候是"裸体"的, 比如前边少了$的时候?
# 当它被赋值的时候, 而不是被引用的时候.
# 赋值
a=879
echo "The value of \"a\" is $a."
# 使用'let'赋值
let a=16+5
echo "The value of \"a\" is now $a."
echo
# 在'for'循环中(事实上, 这是一种伪赋值):
echo -n "Values of \"a\" in the loop are: "
for a in 7 8 9 11
do
echo -n "$a "
done
echo
echo
# 使用'read'命令进行赋值(这也是一种赋值的类型):
echo -n "Enter \"a\" "
read a
echo "The value of \"a\" is now $a."
echo
exit 0
#!/bin/bash
a=23 # 简单的赋值
echo $a
b=$a
echo $b
# 现在让我们来点小变化(命令替换).
a=`echo Hello!` # 把'echo'命令的结果传给变量'a'
echo $a
# 注意, 如果在一个#+的命令替换结构中包含一个(!)的话,
#+ 那么在命令行下将无法工作.
#+ 因为这触发了Bash的"历史机制."
# 但是, 在脚本中使用的话, 历史功能是被禁用的, 所以就能够正常的运行.
a=`ls -l` # 把'ls -l'的结果赋值给'a'
echo $a # 然而, 如果没有引号的话将会删除ls结果中多余的tab和换行符.
echo
echo "$a" # 如果加上引号的话, 那么就会保留ls结果中的空白符.
# (具体请参阅"引用"的相关章节.)
exit 0
使用 $(...) 机制来进行变量赋值(这是一种比后置引用(反引号`)更新的一种方法).
R=$(cat /etc/redhat-release)
arch=$(uname -m)
本质上, Bash变量都是字符串. 但是依赖于具体 的上下文, Bash也允许比较操作和整数操作. 其中的关键因素就是, 变量中的值是否只有数字.
#!/bin/bash
# int-or-string.sh: 整型还是字符串?
a=2334 # 整型.
let "a += 1"
echo "a = $a " # a = 2335
echo # 还是整型.
b=${a/23/BB} # 将"23"替换成"BB".
# 这将把变量b从整型变为字符串.
echo "b = $b" # b = BB35
declare -i b # 即使使用declare命令也不会对此有任何帮助.
echo "b = $b" # b = BB35
let "b += 1" # BB35 + 1 =
echo "b = $b" # b = 1
echo
c=BB34
echo "c = $c" # c = BB34
d=${c/BB/23} # 将"BB"替换成"23".
# 这使得变量$d变为一个整形.
echo "d = $d" # d = 2334
let "d += 1" # 2334 + 1 =
echo "d = $d" # d = 2335
echo
# null变量会如何呢?
e=""
echo "e = $e" # e =
let "e += 1" # 算术操作允许一个null变量?
echo "e = $e" # e = 1
echo # null变量将被转换成一个整型变量.
# 如果没有声明变量会怎样?
echo "f = $f" # f =
let "f += 1" # 算术操作能通过么?
echo "f = $f" # f = 1
echo # 未声明的变量将转换成一个整型变量.
# 所以说Bash中的变量都是不区分类型的.
exit 0
这种变量只有在代码块或者函数中(参见函数中的局部变量)才可见.
这种变量将影响用户接口和shell的行为
如果一个脚本要设置一个环境变量, 那么需要将这些变量"export"出来, 也就是需要通知到脚本 本地的环境. 这是export命令的功能.
一个脚本只能够export变量到这个脚本所产生的子进程, 也就是说只能够对 这个脚本所产生的命令和进程起作用. 如果脚本是从命令行中调用的, 那么 这个脚本所export的变量是 不能 影响命令行环境的. 也就是说, 子进程是不 能够export变量来影响产生自己的父进程的环境的.
从命令行传递到脚本的参数: $0 , $1 , $2 , $3 . . .
$0 就是脚本文件自身的名字, $1 是第一个参数, $2 是第二个参数, $3 是第三个参数, 然后是第 四个. $9 之后的位置参数就必须用大括号括起来了, 比如, ${10 , ${11 , ${12 . 两个比较特殊的变量$*和$@ 表示 所有的 位置参数.}}}
#!/bin/bash
# 作为用例, 调用这个脚本至少需要10个参数, 比如:
# ./scriptname 1 2 3 4 5 6 7 8 9 10
MINPARAMS=10
echo
echo "The name of this script is \"$0\"."
# 添加./是表示当前目录
echo "The name of this script is \"`basename $0`\"."
# 去掉路径名, 剩下文件名, (参见'basename')
echo
if [ -n "$1" ] # 测试变量被引用.
then
echo "Parameter #1 is $1" # 需要引用才能够转义"#"
fi
if [ -n "$2" ]
then
echo "Parameter #2 is $2"
fi
if [ -n "$3" ]
then
echo "Parameter #3 is $3"
fi
# ...
if [ -n "${10}" ] # 大于$9的参数必须用{}括起来.
then
echo "Parameter #10 is ${10}"
fi
echo "-----------------------------------"
echo "All the command-line parameters are: "$*""
if [ $# -lt "$MINPARAMS" ]
then
echo
echo "This script needs at least $MINPARAMS command-linearguments!"
fi
echo
exit 0
如果脚本需要一个命令行参数, 而在调用的时候, 这个参数没被提供, 那么 这就可能造成给这个参数赋一个null变量, 通常情况下, 这都会产生问题. 一种解决这个问题的办法就是使用添加额外字符的方法, 在使用这个位置参 数的变量和位置参数本身的后边全部添加同样的额外字符
variable1_=$1_ # 而不是 variable1=$1
# 这将阻止报错, 即使在调用时没提供这个位置参数.
3
critical_argument01=$variable1_
# 这个扩展的字符是可以被消除掉的, 就像这样.
variable1=${variable1_/_/}
# 副作用就是$variable1_多了一个下划线.
# 这里使用了参数替换模版的一种形式(后边会有具体的讨论).
# (在一个删除动作中, 节省了一个替换模式.)
# 处理这个问题的一个更简单的办法就是
#+ 判断一下这个位置参数是否传递下来了.
if [ -z $1 ]
then
exit $E_MISSING_POS_PARAM
fi
# 然而, 象Fabian Kreutz所指出的那样,上边的方法将可能产生一个意外的副作用.
# 参数替换才是更好的方法:${1:-$DefaultVal}
# 具体参见"参数替换"的相关章节在"变量重游"那章.
shift命令
#!/bin/bash
# 使用'shift'来逐步存取所有的位置参数.
# 给脚本命个名, 比如shift,
#+ 然后给脚本传递一些位置参数, 比如:
# ./shft a b c def 23 skidoo
until [ -z "$1" ] # 直到所有的位置参数都被存取完...
do
echo -n "$1 "
shift
done
echo # 额外的换行.
exit 0
引用的字面意思就是将字符串用双引号括起来. 它的作用就是保护字符串中的特殊字符不被shell或者 shell脚本重新解释, 或者扩展.
grep '[Ff]irst' *.txt
file1.txt:This is the first line of file1.txt.
file2.txt:This is the First line of file2.txt.
echo 换行
bash$ echo $(ls -l)
total 8 -rw-rw-r-- 1 bozo bozo 130 Aug 21 12:57 t222.sh -rw-rw-r-- 1 bozo bozo 78
Aug 21 12:57 t71.sh
bash$ echo "$(ls -l)"
total 8
-rw-rw-r-- 1 bozo bozo 130 Aug 21 12:57 t222.sh
-rw-rw-r-- 1 bozo bozo 78 Aug 21 12:57 t71.sh
在一个双引号中通过直接使用变量名的方法来引用变量
variable1="a variable containing five words"
COMMAND This is $variable1 # 用下面7个参数执行COMMAND命令:
# "This" "is" "a" "variable" "containing" "five" "words"
COMMAND "This is $variable1" # 用下面1个参数执行COMMAND命令:
# "This is a variable containing five words"
variable2="" # Empty.
COMMAND $variable2 $variable2 $variable2 # COMMAND将不带参数执行.
COMMAND "$variable2" "$variable2" "$variable2" # COMMAND将以3个空参数来执行.
COMMAND "$variable2 $variable2 $variable2" # COMMAND将以1个参数来执行(2空格).
单引号(' ')操作与双引号基本一样, 但是不允许引用变量, 因为$的特殊意义被关闭了. 在单引号中, 任何 特殊字符都按照字面的意思进行解释, 除了'. 所以说单引号("全引用")是一种比双引号("部分引 用")更严格的引用方法.
#!/bin/bash
# weirdvars.sh: echo出一些诡异变量.
var="'(]\\{}\$\""
echo $var # '(]\{}$"
echo "$var" # '(]\{}$" 和上一句没什么区别.Doesn't make a difference.
echo
IFS='\'
echo $var # '(] {}$" \ 字符被空白符替换了, 为什么?
echo "$var" # '(]\{}$"
# 这个例子由Stephane Chazelas提供.
exit 0
转义 是一种引用单个字符的方法. 一个前面放上转义符 ()的字符就是告诉shell这个字符按照字面的 意思进行解释, 换句话说, 就是这个字符失去了它的特殊含义.
#!/bin/bash
# escaped.sh: 转义符
echo; echo
echo "\v\v\v\v" # 逐字的打印\v\v\v\v.
# 使用-e选项的'echo'命令来打印转义符.
echo "============="
echo "VERTICAL TABS"
echo -e "\v\v\v\v" # 打印4个垂直制表符.
echo "=============="
echo "QUOTATION MARK"
echo -e "\042" # 打印" (引号, 8进制的ASCII 码就是42).
echo "=============="
# 如果使用$'\X'结构,那-e选项就不必要了.
echo; echo "NEWLINE AND BEEP"
echo $'\n' # 新行.
echo $'\a' # 警告(蜂鸣).
echo "==============="
echo "QUOTATION MARKS"
# 版本2以后Bash允许使用$'\nnn'结构.
# 注意在这里, '\nnn\'是8进制的值.
echo $'\t \042 \t' # 被水平制表符括起来的引号(").
# 当然,也可以使用16进制的值,使用$'\xhhh' 结构.
echo $'\t \x22 \t' # 被水平制表符括起来的引号(").
# 感谢, Greg Keraunen, 指出了这点.
# 早一点的Bash版本允许'\x022'这种形式.
echo "==============="
echo
# 分配ASCII字符到变量中.
# ----------------------------------------
quote=$'\042' # " 被赋值到变量中.
echo "$quote This is a quoted string, $quote and this lies outside
the quotes."
echo
# 变量中的连续的ASCII字符.
triple_underline=$'\137\137\137' # 137是八进制的'_'.
echo "$triple_underline UNDERLINE $triple_underline"
echo
ABC=$'\101\102\103\010' # 101, 102, 103是八进制码的A, B, C.
echo $ABC
echo; echo
escape=$'\033' # 033 是八进制码的esc.
echo "\"escape\" echoes as $escape"
# 没有变量被输出.
echo; echo
exit 0
exit 被用来结束一个脚本, 就像在 C 语言中一样. 它也返回一个值, 并且这个值会传递给脚本的父进 程, 父进程会使用这个值做下一步的处理.
不带参数的exit命令与 exit $?的效果是一样的, 甚至脚本的结尾不写exit, 也与前两者 的效果相同.
$? 保存了最后所执行的命令的退出状态码. 当函数返回之后, $? 保存函数中最后所执行的命令的退出状 态码. 这就是bash对函数"返回值"的处理方法. 当一个脚本退出, $? 保存了脚本的退出状态码, 这个退 出状态码也就是脚本中最后一个执行命令的退出状态码. 一般情况下, 0 表示成功, 在范围1 - 255的整 数表示错误.
#!/bin/bash
echo hello
echo $? # 退出状态为0, 因为命令执行成功.
lskdf # 无效命令.
echo $? # 非零的退出状态, 因为命令执行失败.
echo
exit 113 # 返回113退出状态给shell.
# 为了验证这个结果, 可以在脚本结束的地方使用"echo $?".
# 一般的, 'exit 0' 表示成功,
#+ 而一个非零的退出码表示一个错误, 或者是反常的条件.
- if/then结构用来判断命令列表的退出状态码是否为0(因为在UNIX惯例, 0表示"成功"), 如果成 功的话, 那么就执行接下来的一个或多个命令.
- 有一个专有命令[ (左中括号, 特殊字符). 这个命令与test命令等价, 并且出于效率上的考虑, 这是一个内建命令. 这个命令把它的参数作为比较表达式或者作为文件测试, 并且根据比较的结 果来返回一个退出状态码(0 表示真, 1表示假).
- 在版本2.02的Bash中, 引入了[[ ... ]] 扩展测试命令 , 因为这种表现形式可能对某些语言的程序 员来说更容易熟悉一些. 注意[[是一个关键字, 并不是一个命令. Bash把 [[ $a -lt $b ]] 看作一个单独的元素, 并且返回一个退出状态码. (( ... ))和let ...结构也能够返回退出状态码, 当它们所测试的算术表达式的结果为非零的时 候, 将会返回退出状态码0. 这些算术扩展结构被用来做算术比较.
if cmp a b &> /dev/null # 禁止输出.
then echo "Files a and b are identical."
else echo "Files a and b differ."
fi
# 非常有用的"if-grep"结构:
# ------------------------
if grep -q Bash file
then echo "File contains at least one occurrence of Bash."
fi
word=Linux
letter_sequence=inu
if echo "$word" | grep -q "$letter_sequence"
# "-q" 选项是用来禁止输出的.
then
echo "$letter_sequence found in $word"
else
echo "$letter_sequence not found in $word"
fi
if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED
then echo "Command succeeded."
else echo "Command failed."
fi
一个if/then结构可以包含嵌套的比较操作和条件判断操作.
if echo "Next *if* is part of the comparison for the first *if*."
if [[ $comparison = "integer" ]]
then (( a < b ))
else
[[ $a < $b ]]
fi
then
echo '$a is less than $b'
fi
#!/bin/bash
# 小技巧:
# 如果你不能够确定一个特定的条件该如何进行判断,
#+ 那么就使用if-test结构.
echo
echo "Testing \"0\""
if [ 0 ] # zero
then
echo "0 is true."
else
echo "0 is false."
fi # 0 为真.
echo
echo "Testing \"1\""
if [ 1 ] # one
then
echo "1 is true."
else
echo "1 is false."
fi # 1 为真.
echo
echo "Testing \"-1\""
if [ -1 ] # 负1
then
echo "-1 is true."
else
echo "-1 is false."
fi # -1 为真.
echo
echo "Testing \"NULL\""
if [ ] # NULL (空状态)
then
echo "NULL is true."
else
echo "NULL is false."
fi # NULL 为假.
echo
echo "Testing \"xyz\""
if [ xyz ] # 字符串
then
echo "Random string is true."
else
echo "Random string is false."
fi # 随便的一串字符为真.
echo
echo "Testing \"\$xyz\""
if [ $xyz ] # 判断$xyz是否为null, 但是...
# 这只是一个未初始化的变量.
then
echo "Uninitialized variable is true."
else
echo "Uninitialized variable is false."
fi # 未定义的初始化为假.
echo
echo "Testing \"-n \$xyz\""
if [ -n "$xyz" ] # 更加正规的条件检查.
then
echo "Uninitialized variable is true."
else
echo "Uninitialized variable is false."
fi # 未初始化的变量为假.
echo
xyz= # 初始化了, 但是赋null值.
echo "Testing \"-n \$xyz\""
if [ -n "$xyz" ]
then
echo "Null variable is true."
else
echo "Null variable is false."
fi # null变量为假.
echo
# 什么时候"false"为真?
echo "Testing \"false\""
if [ "false" ] # 看起来"false"只不过是一个字符串而已.
then
echo "\"false\" is true." #+ 并且条件判断的结果为真.
else
echo "\"false\" is false."
fi # "false" 为真.
echo
echo "Testing \"\$false\"" # 再来一个, 未初始化的变量.
if [ "$false" ]
then
echo "\"\$false\" is true."
else
echo "\"\$false\" is false."
fi # "$false" 为假.
# 现在, 我们得到了预期的结果.
# 如果我们测试以下为初始化的变量"$true"会发生什么呢?
echo
exit 0
如果 if 和 then 在条件判断的同一行上的话, 必须使用分号来结束 if 表达式.
elif 是else if的缩写形式.
if [ condition1 ] then command1 command2 command3 elif [ condition2 ]
#与else if一样
then command4 command5 else default-command fi
if test condition-true 结构与 if [ condition-true ] 完全相同.
bash$ type test
test is a shell builtin
bash$ type '['
[ is a shell builtin
bash$ type '[['
[[ is a shell keyword
bash$ type ']]'
]] is a shell keyword
bash$ type ']'
bash: type: ]: not found
[[ ]]结构比[ ]结构更加通用. 这是一个 扩展的test命令 , 是从 ksh88 中引进的.在[[和]]之间所有的字符都不会发生文件名扩展或者单词分割, 但是会发生参数扩展和命令替换
file=/etc/passwd
if [[ -e $file ]]
then
echo "Password file exists."
fi
使用[[ ... ]]条件判断结构, 而不是[ ... ], 能够防止脚本中的许多逻辑错误. 比如, &&, ||, <, 和> 操作符能够正常存在于[[ ]]条件判断结构中, 但是如果出现在[ ]结构中 的话, 会报错
(( ))结构扩展并计算一个算术表达式的值.
#!/bin/bash
# 算术测试.
# (( ... ))结构可以用来计算并测试算术表达式的结果.
# 退出状态将会与[ ... ]结构完全相反!
(( 0 ))
echo "Exit status of \"(( 0 ))\" is $?." # 1
(( 1 ))
echo "Exit status of \"(( 1 ))\" is $?." # 0
(( 5 > 4 )) # 真
echo "Exit status of \"(( 5 > 4 ))\" is $?." # 0
(( 5 > 9 )) # 假
echo "Exit status of \"(( 5 > 9 ))\" is $?." # 1
(( 5 - 5 )) # 0
echo "Exit status of \"(( 5 - 5 ))\" is $?." # 1
(( 5 / 4 )) # 除法也可以.
echo "Exit status of \"(( 5 / 4 ))\" is $?." # 0
(( 1 / 2 )) # 除法的计算结果 < 1.
echo "Exit status of \"(( 1 / 2 ))\" is $?." # 截取之后的结果为 0.
# 1
(( 1 / 0 )) 2>/dev/null # 除数为0, 非法计算.
# ^^^^^^^^^^^
echo "Exit status of \"(( 1 / 0 ))\" is $?." # 1
# "2>/dev/null"起了什么作用?
# 如果这句被删除会怎样?
# 尝试删除这句, 然后在运行这个脚本.
exit 0
如果下面的条件成立将会返回真. -e 文件存在 -a 文件存在 这个选项的效果与-e相同. 但是它已经被"弃用"了, 并且不鼓励使用. -f 表示这个文件是一个 一般 文件(并不是目录或者设备文件) -s 文件大小不为零 -d 表示这是一个目录 -b 表示这是一个块设备(软盘, 光驱, 等等.) -c 表示这是一个字符设备(键盘, modem, 声卡, 等等.) -p 这个文件是一个管道 -h 这是一个符号链接 -L 这是一个符号链接 -S 表示这是一个socket -t 文件(描述符)被关联到一个终端设备上 这个测试选项一般被用来检测脚本中的 stdin ( [ -t 0 ] ) 或者 stdout ( [ -t 1 ] )是否来自于一个 终端. -r 文件是否具有可读权限( 指的是正在运行这个测试命令的用户是否具有读权限 ) -w 文件是否具有可写权限(指的是正在运行这个测试命令的用户是否具有写权限) -x 文件是否具有可执行权限(指的是正在运行这个测试命令的用户是否具有可执行权限) -g set-group-id(sgid)标记被设置到文件或目录上 如果目录具有 sgid 标记的话, 那么在这个目录下所创建的文件将属于拥有这个目录的用户组, 而 不必是创建这个文件的用户组. 这个特性对于在一个工作组中共享目录非常有用. -u set-user-id (suid)标记被设置到文件上 如果一个 root 用户所拥有的二进制可执行文件设置了 set-user-id 标记位的话, 那么普通用户也会 以 root 权限来运行这个文件. [1] 这对于需要访问系统硬件的执行程序(比如pppd和cdrecord)非 常有用. 如果没有 suid 标志的话, 这些二进制执行程序是不能够被非root用户调用的.
-rwsr-xr-t 1 root 178236 Oct 2 2000 /usr/sbin/pppd
对于设置了 suid 标志的文件, 在它的权限列中将会以 s 表示. -k 设置 粘贴位 对于"粘贴位"的一般了解, save-text-mode 标志是一个文件权限的特殊类型. 如果文件设置了这 个标志, 那么这个文件将会被保存到缓存中, 这样可以提高访问速度. [2] 粘贴位如果设置在目 录中, 那么它将限制写权限. 对于设置了粘贴位的文件或目录, 在它们的权限标记列中将会显 示 t .
drwxrwxrwt 7 root 1024 May 19 21:26 tmp/
如果用户并不拥有这个设置了粘贴位的目录, 但是他在这个目录下具有写权限, 那么这个用户只 能在这个目录下删除自己所拥有的文件. 这将有效的防止用户在一个公共目录中不慎覆盖或者删 除别人的文件. 比如说 /tmp 目录. (当然, 目录的所有者或者 root 用户可以随意删除或重命名其中 的文件.) -O 判断你是否是文件的拥有者 -G 文件的group-id是否与你的相同 -N 从文件上一次被读取到现在为止, 文件是否被修改过 f1 -nt f2 文件 f1 比文件 f2 新 f1 -ot f2 文件 f1 比文件 f2 旧 f1 -ef f2 文件 f1 和文件 f2 是相同文件的硬链接 ! "非" -- 反转上边所有测试的结果(如果没给出条件, 那么返回真).
#!/bin/bash
# broken-link.sh
#一个纯粹的shell脚本用来找出那些断掉的符号链接文件并且输出它们所指向的文件.
#以便于它们可以把输出提供给xargs来进行处理 :)
#比如. broken-link.sh /somedir /someotherdir|xargs rm
#
#下边的方法, 不管怎么说, 都是一种更好的办法:
#
#find "somedir" -type l -print0|\
#xargs -r0 file|\
#grep "broken symbolic"|
#sed -e 's/^\|: *broken symbolic.*$/"/g'
#
#但这不是一个纯粹的bash脚本, 最起码现在不是.
#注意: 谨防在/proc文件系统和任何死循环链接中使用!
##############################################################
#如果没有参数被传递到脚本中, 那么就使用
#当前目录. 否则就是用传递进来的参数作为目录
#来搜索.
####################
[ $# -eq 0 ] && directorys=`pwd` || directorys=$@
#编写函数linkchk用来检查传递进来的目录或文件是否是链接,
#并判断这些文件或目录是否存在. 然后打印它们所指向的文件.
#如果传递进来的元素包含子目录,
#那么把子目录也放到linkcheck函数中处理, 这样就达到了递归的目的.
##########
linkchk () {
for element in $1/*; do
[ -h "$element" -a ! -e "$element" ] && echo \"$element\"
[ -d "$element" ] && linkchk $element
# 当然, '-h'用来测试符号链接, '-d'用来测试目录.
done
}
#把每个传递到脚本的参数都送到linkchk函数中进行处理,
#检查是否有可用目录. 如果没有, 那么就打印错误消息和
#使用信息.
################
for directory in $directorys; do
if [ -d $directory ]
then linkchk $directory
else
echo "$directory is not a directory"
echo "Usage: $0 dir1 dir2 ..."
fi
done
exit 0
整数比较
operator | meanings | example |
---|---|---|
-eq | 等于 | if [ "$a" -eq "$b" ] |
-ne | 不等于 | if [ "$a" -ne "$b" ] |
-gt | 大于 | if [ "$a" -gt "$b" ] |
-ge | 大于等于 | if [ "$a" -ge "$b" ] |
-lt | 小于 | if [ "$a" -lt "$b" ] |
-le | 小于等于 | if [ "$a" -le "$b" ] |
< <= | 小于 小于等于(在双括号中使用) | (("$a" < "$b")) |
> >= | 大于 大于等于(在双括号中使用) | (("$a" > "$b")) |
字符串比较
= | meanings | example |
---|---|---|
= | 等于 | if [ "$a" = "$b" ] |
== | 等于 | if [ "$a" == "$b" ] |
!= | 不等号 | if [ "$a" != "$b" ] |
< | 小于, 按照ASCII字符进行排序 | if [ "$a" \< "$b" ] if [ ["$a" < "$b"] ] |
> | 大于, 按照ASCII字符进行排序 | if [ "$a" \> "$b" ] if [ ["$a" > "$b"] ] |
-z | 字符串为"null", 意思就是字符串长度为零 | |
-n | 字符串不为"null". |
[[ $a == z* ]] # 如果$a以"z"开头(模式匹配)那么结果将为真
[[ $a == "z*" ]] # 如果$a与z*相等(就是字面意思完全一样), 那么结果为真.
[ $a == z* ] # 文件扩展匹配(file globbing)和单词分割有效.
[ "$a" == "z*" ] # 如果$a与z*相等(就是字面意思完全一样), 那么结果为真.
#!/bin/bash
a=4
b=5
# 这里的"a"和"b"既可以被认为是整型也可被认为是字符串.
# 这里在算术比较与字符串比较之间是容易让人产生混淆,
#+ 因为Bash变量并不是强类型的.
# Bash允许对于变量进行整形操作与比较操作.
#+ 但前提是变量中只能包含数字字符.
# 不管怎么样, 还是要小心.
echo
if [ "$a" -ne "$b" ]
then
echo "$a is not equal to $b"
echo "(arithmetic comparison)"
fi
echo
if [ "$a" != "$b" ]
then
echo "$a is not equal to $b."
echo "(string comparison)"
# "4" != "5"
# ASCII 52 != ASCII 53
fi
# 在这个特定的例子中, "-ne"和"!="都可以.
echo
exit 0
-a 逻辑与
-o 逻辑或
这与Bash中的比较操作符&&和||非常相像, 但是这个两个操作符是用在双中括号结构中的.
if [ condition1 ] then if [ condition2 ] then do-something # But only if both "condition1" and "condition2" valid. fi fi
operator | meanings |
---|---|
= | 赋值 |
+ - * / | 加减乘除 |
** | 幂运算 |
% | 模运算 |
+= -= *= /= %= | x 等于 |
<< | 左移位(每次左移都相当于乘以 2 ) |
<<= | 左移赋值 |
>>= | 右移赋值 |
& &= | 按位与 按位与赋值 |
| |= | 按位或 按位或赋值 |
~ | 按位反 |
! | 按位非 |
^ ^= | 按位异或XOR 按位异或赋值 |
&& | 与 |
|| | 或 |
, | 逗号操作符 连接两个或多个算术运 |
#!/bin/bash
# 使用10种不同的方法计数到11.
n=1; echo -n "$n "
let "n = $n + 1" # let "n = n + 1" 也可以.
echo -n "$n "
: $((n = $n + 1))
# ":" 是必需的, 因为如果没有":"的话,
#+ Bash将会尝试把"$((n = $n + 1))"解释为一个命令.
echo -n "$n "
(( n = n + 1 ))
# 上边这句是一种更简单方法.
echo -n "$n "
n=$(($n + 1))
echo -n "$n "
: $[ n = $n + 1 ]
# ":" 是必需的, 因为如果没有":"的话,
#+ Bash将会尝试把"$[ n = $n + 1 ]"解释为一个命令.
# 即使"n"被初始化为字符串, 这句也能够正常运行.
echo -n "$n "
n=$[ $n + 1 ]
# 即使"n"被初始化为字符串, 这句也能够正常运行.
#* 应该尽量避免使用这种类型的结构, 因为它已经被废弃了, 而且不具可移植性.
echo -n "$n "
# 现在来一个C风格的增量操作.
let "n++" # let "++n" 也可以.
echo -n "$n "
(( n++ )) # (( ++n ) 也可以.
echo -n "$n "
: $(( n++ )) # : $(( ++n )) 也可以.
echo -n "$n "
: $[ n++ ] # : $[ ++n ]] 也可以.
echo -n "$n "
echo
exit 0
Bash不能够处理浮点运算
#!/bin/bash
a=24
b=47
if [ "$a" -eq 24 ] && [ "$b" -eq 47 ]
then
echo "Test #1 succeeds."
else
echo "Test #1 fails."
fi
# ERROR: if [ "$a" -eq 24 && "$b" -eq 47 ]
#+ 尝试运行' [ "$a" -eq 24 '
#+ 因为没找到匹配的']'所以失败了.
#
# 注意: if [[ $a -eq 24 && $b -eq 24 ]] 也能正常运行.
# 双中括号的if-test结构要比
#+ 单中括号的if-test结构更加灵活.
# (在第17行"&&"与第6行的"&&"具有不同的含义.)
if [ "$a" -eq 98 ] || [ "$b" -eq 47 ]
then
echo "Test #2 succeeds."
else
echo "Test #2 fails."
fi
# -a和-o选项提供了
#+ 一种可选的混合条件测试的方法.
if [ "$a" -eq 24 -a "$b" -eq 47 ]
then
echo "Test #3 succeeds."
else
echo "Test #3 fails."
fi
if [ "$a" -eq 98 -o "$b" -eq 47 ]
then
echo "Test #4 succeeds."
else
echo "Test #4 fails."
fi
a=rhino
b=crocodile
if [ "$a" = rhino ] && [ "$b" = crocodile ]
then
echo "Test #5 succeeds."
else
echo "Test #5 fails."
fi
exit 0
逗号
let "t1 = ((5 + 3, 7 - 1, 15 - 4))"
echo "t1 = $t1" # t1 = 11
let "t2 = ((a = 9, 15 / 3))" # 设置"a"并且计算"t2".
echo "t2 = $t2 a = $a" # t2 = 5 a = 9
shell脚本在默认情况下都是把数字作为10进制数来处理, 除非这个数字采用了特殊的标记或者前缀. 如果数字以 0 开头的话那么就是 8进制 数. 如果数字以 0x 开头的话那么就是 16进制 数. 如果数字中间嵌入 了 # 的话, 那么就被认为是 BASE#NUMBER 形式的标记法(有范围和符号限制).
#!/bin/bash
# numbers.sh: 几种不同数制的数字表示法.
# 10进制: 默认情况
let "dec = 32"
echo "decimal number = $dec" # 32
# 这没什么特别的.
# 8进制: 以'0'(零)开头
let "oct = 032"
echo "octal number = $oct" # 26
# 表达式结果是用10进制表示的.
# ---------------------------
# 16进制: 以'0x'或者'0X'开头的数字
let "hex = 0x32"
echo "hexadecimal number = $hex" # 50
# 表达式结果是用10进制表示的.
# 其他进制: BASE#NUMBER
# BASE的范围在2到64之间.
# NUMBER的值必须使用BASE范围内的符号来表示, 具体看下边的示例.
let "bin = 2#111100111001101"
echo "binary number = $bin" # 31181
let "b32 = 32#77"
echo "base-32 number = $b32" # 231
let "b64 = 64#@_"
echo "base-64 number = $b64" # 4031
# 这个表示法只能工作于受限的ASCII字符范围(2 - 64).
# 10个数字 + 26个小写字母 + 26个大写字符 + @ + _
echo
echo $((36#zz)) $((2#10101010)) $((16#AF16)) $((53#1aA))
# 1295 170 44822 3375
# 重要的注意事项:
# ---------------
# 使用一个超出给定进制的数字的话,
#+ 将会引起一个错误.
let "bad_oct = 081"
# (部分的) 错误消息输出:
# bad_oct = 081: value too great for base (error token is "081")
# Octal numbers use only digits in the range 0 - 7.
exit 0