当我们执行一些较长的命令时,重复的输入会非常麻烦,这时我们可以为这类命令创建一个简短的别名,创建别名使用 alias 命令。
# 创建别名
alias new_command='command sequence'
alias install='sudo apt-get install'
# 删除原始文件,同时在backup目录中保留副本。
alias rm='cp $@ ~/backup && rm $@'
# 列出当前定义的所有别名
alias
alias 命令的效果只是暂时的。一旦关闭当前终端,所有设置过的别名就失效了。为了使别名在所有的shell中都可用,可以将其定义放入~/.bashrc文件中。每当一个新的交互式shell进程生成时,都会执行 ~/.bashrc中的命令。
echo 'alias cmd="command seq"' >> ~/.bashrc
如果需要删除别名,只需将其对应的定义(如果有的话)从~/.bashrc中删除,或者使用unalias 命令。也可以使用 alias example= ,这会取消别名 example
unalias example
或者alias example=
创建别名时,如果已经有同名的别名存在,那么原有的别名设置将被新的设置取代。
延时可以用来在程序执行过程中等待一段时间(比如1秒),或是每隔几秒钟(或是几个月)监督某项任务。日期能够以多种格式呈现。在系统内部,日期被存储成一个整数,其取值为自1970年1月1
日0时0分0秒起所流逝的秒数。这种计时方式称为纪元时或Unix时间。 下边是一些常见的使用方式:
# 打印日期
date # Fri 19 Aug 2022 04:04:08 AM UTC
# 打印纪元时
date +%s # 1660882402
# 将日期转换为纪元时
date --date "Wed mar 15 08:09:16 EDT 2017" +%s # 1489579718
# 打印某天属于星期几
date --date "Jan 20 2001" +%A # Saturday
date +%A
# 设置系统日期
date -s "21 June 2009 11:01:22"
上述例子中,通过使用带前缀 +
的字符串,可以指定所需要打印的日期信息,类似的字符串还有如下:
日期内容 | 格式 |
---|---|
工作日(weekday) | %a(例如:Sat) %A(例如:Saturday) |
月 | %b(例如:Nov) %B(例如:November) |
日 | %d(例如:31) |
特定格式日期(mm/dd/yy) | %D(例如:10/18/10) |
年 | %y(例如:10) %Y(例如:2010) |
小时 | %I或%H(例如:08) |
分钟 | %M(例如:33) |
秒 | %S(例如:10) |
纳秒 | %N(例如:695208515) |
Unix纪元时 | %s(例如:1290049486) |
对一段代码进行计时:
#!/bin/bash
#文件名: time_take.sh
start=$(date +%s)
commands;
statements;
end=$(date +%s)
difference=$(( end - start))
echo Time taken to execute commands is $difference seconds.
所有编程语言都应该实现的一个特性就是在出现始料未及的情况时,能够生成跟踪信息。调试信息可以帮你弄清楚是什么原因使得程序行为异常。
Bash 的调试主要有以下三种调试方式:
一、使用 Bash 内建的调试工具:
使用 -x
选项运行脚本:bash -x script.sh
,会打印出每行命令以及当前的状态。
使用 set -x
和 set +x
可以对脚本的部分内容进行调试。
#!/bin/bash
#文件名: debug.sh
for i in {1..6};
do
set -x
echo $i
set +x
done
echo "Script executed"
对
-x
和+x
之间的区域进行调试
二、通过 _DEBUG
环境变量生成调试信息
#!/bin/bash
function DEBUG()
{
[ "$_DEBUG" == "on" ] && $@ || :
}
for i in {1..10}
do
DEBUG echo "I is $i"
done
_DEBUG=on ./script.sh
,设置_DEBUG=on
来运行
三、使用 shebang 进行调试
把 shebang 从 #!/bin/bash
改成 #!/bin/bash -xv
,这样一来,不用任何其他选项就可以启用调试功能了。
sh -x testScript.sh 2> debugout.txt
可以将调试的信息输出到指定的文件。
函数的定义包括function命令、函数名、开/闭括号以及包含在一对花括号中的函数体。
# 函数的定义
function fname()
{
statements;
}
fname()
{
statements;
}
# 函数的调用,直接使用函数名调用即可
fname;
# 函数参数可以按位置访问, $1 是第一个参数, $2 是第二个参数,
fname arg1 arg2;
fname()
{
echo $1, $2; #访问参数1和参数2
echo "$@"; #以列表的方式一次性打印所有参数
echo "$*"; #类似于$@,但是所有参数被视为单个实体
return 0; #返回值
}
与其它语言不同的是,bash 的函数不需要形式参数,而是通过以下特定的方式访问。
$0 是脚本名称。
$1 是第一个参数。
$2 是第二个参数。
$n 是第n个参数。
“$@” 被扩展成 “$1” “$2” “$3” 等。
“$*” 被扩展成 “$1c$2c$3” ,其中 c 是IFS的第一个字符。
Unix shell脚本最棒的特性之一就是可以轻松地将多个命令组合起来生成输出。一个命令的输出可以作为另一个命令的输入,而这个命令的输出又会传递至下一个命令,以此类推。
命令输入通常来自于 stdin 或参数。输出可以发送给 stdout 或 stderr 。当我们组合多个命令时,通常将 stdin 用于输入, stdout 用于输出。
在这种情况下,这些命令被称为过滤器(filter)。我们使用管道(pipe)连接每个过滤器,管道操作符是 | 。例如:comd1 | comd2 | comd3
我们通常使用管道并配合子shell的方式来组合多个命令的输出。
# 1. 组合两个命令
ls | cat -n > out.txt
# 2. 子 shell 法
cmd_output=$(ls | cat -n)
echo $cmd_output
# 3. 反引用
cmd_output=`ls | cat -n`
echo $cmd_output
- ls (列出当前目录内容)的输出被传给 cat -n ,后者为通过 stdin 所接收到的输入内容加上行号,然后将输出重定向到文件out.txt。
- 将命令的输出赋值给变量,之后以命令行参数的形式传递给下一跳命令
- 类似子 shell 法,也是先将命令的输出先赋值给变量
子shell本身就是独立的进程。可以使用 () 操作符来定义一个子shell。
pwd
(cd /bin; ls)
当命令在子shell中执行时,不会对当前shell造成任何影响;所有的改变仅限于该子shell内。例如,当用 cd 命令改变子shell的当前目录时,这种变化不会反映到主shell环境中。
假设我们使用子shell或反引用的方法将命令的输出保存到变量中,为了保留输出的空格和换行符( \n ),必须使用双引号。
$ cat text.txt
1
2
3
$ out=$(cat text.txt)
$ echo $out
1 2 3 # 丢失了1、2、3中的\n
$ out="$(cat text.txt)"
$ echo $out
1
2
3
Bash命令 read 能够从键盘或标准输入中读取文本。
# 从输入中读取n个字符并存入变量 var
read -n 2 var
# 用无回显的方式读取密码:
read -s var
# 显示提示信息
read -p "Enter input:" var
# 给定的时间内输入,单位是 秒
read -t 2 var
# 用特定的定界符作为输入行的结束:
read -d ":" var
有时候命令只有在满足某些条件时才能够成功执行。例如,在下载文件之前必须先创建该文件。这种情况下,你可能希望重复执行命令,直到成功为止。
首先我们可以定义如下形式的函数:
repeat()
{
while true
do
$@ && return
done
}
调用该函数会重复执行 while 循环,直到命令执行成功,才返回。
为了方便可以将该函数放入shell的rc文件,更便于使用。
repeat() { while true; do $@ && return; done }
但上述函数的一个缺点是每执行一次 while 循环都会生成一个新的进程,因为在 shell 中 true 是通过 /bin 目录下一个二进制文件实现的。
为了避免这种情况,可以使用shell的内建命令 : ,该命令的退出状态总是为0:
repeat() { while :; do $@ && return; done }
如果我们事先知道某条命令的执行需要耗费一定的时间,比如执行下载某个文件命令,那么直接执行上述命令会造成的一个结果是会产生大量对目标服务器的下载请求。有可能会被目标服务器拉黑,因此我们可以在上述函数中加入一段时延,
repeat() { while :; do $@ && return; sleep 30; done }
没 30s 执行一次
shell 中的命令分为内建命令和外部命令。前者是构建在shell内部;后者是一个独立的文件(可以是二进制文件,也可以是一个脚本)
**内部命令:**实际上是shell程序的一部分,其中包含的是一些比较简单的linux系统命令,这些命令由shell程序识别并在shell程序内部完成运行,通常在linux系统加载运行时shell就被加载并驻留在系统内存中。内部命令是写在bash源码里面的,其执行速度比外部命令快,因为解析内部命令shell不需要创建子进程。比如:exit,history,cd,echo等。
外部命令:它的执行比较复杂,shell进程会fork一个子进程,父进程随后挂起,然后在子进程中exec加载外部文件,子进程返回后,父进程才继续执行。
判断方式:我们可以使用 type
命令判断某个命令是否是内建命令
type cd # cd is a shell builtin
type ifconfig # ifconfig is /usr/sbin/ifconfig
在 shell 中每一条命令或者表达式执行完之后都会返回一个状态码,在条件判断的时候会根据该状态码进行判断,0 被认为为真,1 被认为为假。可以通过$?
获取最近执行命令或表达式的状态码
true
$? # 0
false
$? # 1
[ 1 == 1 ]
$? # 0
[ 1 == 2 ]
$? # 1
内部字段分隔符(Internal Field Separator,IFS)是shell脚本编程中的一个重要概念。作为分隔符,IFS有其特殊用途。它是一个环境变量,其中保存了用于分隔的字符。它是当前shell环境使用的默认定界字符串。默认情况下,系统的 IFS 默认为空白字符(换行符、制表符或者空格)
#!/bin/bash
# IFS 设置为逗号
data="name, gender,rollno,location"
oldIFS=$IFS
IFS="," #IFS现在被设置为,
for item in $data;
do
echo Item: $item
done
IFS=$oldIFS
# Item: name
# Item: gender
# Item: rollno
# Item: location
# -------------------------
# IFS 为默认值
data="name, gender,rollno,location"
oldIFS=$IFS
for item in $data;
do
echo Item: $item
done
IFS=$oldIFS
# Item: name,
# Item: gender,rollno,location
for 循环
#!/bin/bash
for var in {a..z}; do
echo ${var}
done
for ((i=0;i<10;++i)); do
echo ${i}
done
**注意:**运行的时候需要使用 bash xxx.sh,sh xxx.sh 不可以,不支持 {…} 扩展。
sh(Bourne Shell)是一个早期的重要shell,1978年由史蒂夫·伯恩编写,并同Version 7 Unix一起发布。
bash(Bourne-Again Shell)是一个为GNU计划编写的Unix shell。1987年由布莱恩·福克斯创造。主要目标是与POSIX标准保持一致,同时兼顾对sh的兼容,是各种Linux发行版标准配置的Shell,在Linux系统上/bin/sh往往是指向/bin/bash的符号链接。
while 循环
#!/bin/bash
i=0
while(( ${i}<10 ))
do
echo ${i}
let "i++"
done
until 循环
x=0;
until [ $x -eq 9 ]; #条件是[$x -eq 9 ]
do
let x++; echo $x;
done
当条件不为真时就一直循环,直到条件为真时截止
if
if condition;
then
commands;
fi
if condition;
then
commands;
else if condition; then
commands;
else
commands;
fi
算术比较
[ $var -eq 0 ] #当$var等于0时,返回真 [ $var -ne 0 ] #当$var不为0时,返回真
需要注意,在 [ 或 ] 与操作数之间有一个空格。如果忘记了这个空格,脚本就会报错。
-gt :大于
-lt :小于
-ge :大于或等于。
-le :小于或等于。
-a :逻辑与
-o :逻辑或
!:逻辑非
文件系统相关
[ -f $file_var ] :如果给定的变量包含正常的文件路径或文件名,则返回真
[ -x $var ] :如果给定的变量包含的文件可执行,则返回真
[ -d $var ] :如果给定的变量包含的是目录,则返回真
[ -e $var ] :如果给定的变量包含的文件存在,则返回真
[ -c $var ] :如果给定的变量包含的是一个字符设备文件的路径,则返回真
[ -b $var ] :如果给定的变量包含的是一个块设备文件的路径,则返回真
[ -w $var ] :如果给定的变量包含的文件可写,则返回真
[ -r $var ] :如果给定的变量包含的文件可读,则返回真
[ -L $var ] :如果给定的变量包含的是一个符号链接,则返回真
字符串比较
进行字符串比较时,最好用双中括号,因为有时候采用单个中括号会产生错误。
注意,双中括号是Bash的一个扩展特性。如果出于性能考虑,使用ash或dash来运行脚本,那么将无法使用该特性。
# 测试两个字符串是否相等
[[ $str1 = $str2 ]]
[[ $str1 == $str2 ]]
[[ $str1 != $str2 ]]
# 按字典序比较
[[ $str1 > $str2 ]]
[[ $str1 < $str2 ]]
# 判断是否为空串
[[ -z $str1 ]]
# 判断是否为非空串
[[ -n $str1 ]]
- 等于号前后需要加上空格,不然会变成赋值操作
上述括号可以使用 test 命令进行替代,避免过多使用 [],增加代码的可读性。
if test $var -eq 0 ; then echo "True"; fi
test 是一个外部程序,需要衍生出对应的进程,而 [ 是Bash的一个内部函数,因此后者的执行效率更高。 test 兼容于Bourne shell、ash、dash等。
的一个扩展特性。如果出于性能考虑,使用ash或dash来运行脚本,那么将无法使用该特性。
# 测试两个字符串是否相等
[[ $str1 = $str2 ]]
[[ $str1 == $str2 ]]
[[ $str1 != $str2 ]]
# 按字典序比较
[[ $str1 > $str2 ]]
[[ $str1 < $str2 ]]
# 判断是否为空串
[[ -z $str1 ]]
# 判断是否为非空串
[[ -n $str1 ]]
- 等于号前后需要加上空格,不然会变成赋值操作
上述括号可以使用 test 命令进行替代,避免过多使用 [],增加代码的可读性。
if test $var -eq 0 ; then echo "True"; fi
test 是一个外部程序,需要衍生出对应的进程,而 [ 是Bash的一个内部函数,因此后者的执行效率更高。 test 兼容于Bourne shell、ash、dash等。