为了进一步提高效率,逐步用 linux 替代 windows,如果不会编写 shell 脚本则无法发挥命令行的优势。
1 Shell脚本
- Shell 有些独特,因为它不仅是一个功能强大的命令行接口,也是一个脚本语言解释器。
- 一个 shell 脚本就是一个包含一系列命令的文件。shell 读取这个文件,然后执行 文件中的所有命令,就好像这些命令已经直接被输入到了命令行中一样。
2 基本步骤
为了成功地创建和运行一个 shell 脚本,我们需要做三件事情:
- 编写一个脚本
hello_world
。Linux并不根据后缀名判断文件类型,如果命名为hello_world.sh
,执行的时候要输入完整的文件名。基本格式如下:
#!/bin/bash
# This is our first script.
echo 'Hello World!'
这个#!
字符序列是一种特殊的结构,被用来告诉操作系统将执行此脚本所用的解释器的名字。每个 shell 脚本都应该把这一文本行 作为它的第一行。
-
使脚本文件可执行。 使用
chmod
命令,对于脚本文件,有两个常见的权限设置;权限为755的脚本,则每个人都能执行,和权限为700的 脚本,只有文件所有者能够执行。注意为了能够执行脚本,脚本必须是可读的。 -
把脚本放置到 shell 能够找到的地方。为了能够运行此脚本,我们必须指定脚本文件明确的路径,如下:
[me@linuxbox ~]$ ./hello_world
Hello World!
如果没有给出可执行程序的明确路径名,那么系统每次都会搜索一系列的目录,来查找此可执行程序。这个目录列表被存储在一个名为 PATH 的环境变量中。这个 PATH 变量包含一个由冒号分隔开的目录列表。
- 如果我们的脚本位于此列表中任意目录下,那么不明确指定脚本文件路径也可以运行。
- 如果这个 PATH 变量不包含这个目录,我们能够手动添加。比如,在.bashrc 文件中包含下面这一行文本将
~/bin
加入到PATH变量:
export PATH=~/bin:"$PATH"
注意这个命令是向PATH中增加新的目录项,等号左侧是PATH变量,右侧是新的值,其中$PATH
是对PATH的展开(后面会讲)。
当做了这个修改之后,它会在每个新的终端会话中生效。为了把这个修改应用到当前的终端会话中, 我们必须让 shell 重新读取这个 .bashrc 文件。
[me@linuxbox ~]$ . .bashrc
这个点(.)命令是 source 命令的同义词,一个 shell 内建命令,用来读取一个指定的 shell 命令文件, 并把它看作是从键盘中输入的一样。
3 保存位置
~/bin
目录是存放为个人所用脚本的好地方。
如果我们编写了一个脚本,系统中的每个用户都可以使用它,那么这个脚本的传统位置是 /usr/local/bin
。
系统管理员使用的脚本经常放到 /usr/local/sbin
目录下。
大多数情况下,本地支持的软件,不管是脚本还是编译过的程序,都应该放到 /usr/local
目录下, 而不是在 /bin
或 /usr/bin
目录下。
4 格式技巧
- 为了减少输入,当在命令行中输入选项的时候,短选项更受欢迎,但是当书写脚本的时候, 长选项能提供可读性。
- 当使用长命令的时候,通过把命令在几个文本行中展开,可以提高命令的可读性。
find playground
(
-type f
-not -perm 0600
-exec chmod 0600 ‘{}’ ‘;’
)
-or
(
-type d
-not -perm 0711
-exec chmod 0711 ‘{}’ ‘;’
)
5 变量
- 当 shell 碰到一个变量的时候,它会自动地创建它。这不同于许多编程语言,它们中的变量在使用之前,必须显式的声明或是定义。
- shell 不会在乎变量值的类型;它把它们都看作是字符串。
6 展开
6.1 命令展开
把一个命令的输出作为一个展开模式来使用:
[me@linuxbox ~]$ ls -l $(which cp)
-rwxr-xr-x 1 root root 71516 2007-12-05 08:58 /bin/cp
6.2 字符串展开
- 基本用法
所有的变量存储的都是字符串,我们在使用这些字符串时有时候需要进行简单的处理,比如只取一部分、去掉后缀名等等。下面的命令就是按照特定要求展开字符串:
var = /home/me/video/maopian.avi
命令格式 | 功能 |
---|---|
${var} |
完全展开字符串。 |
${#var} |
展开为字符串var的长度。 |
${var:offset} |
提取字符串var中第 offset 个字符到末尾的部分。 |
${var:offset:length} |
提取字符串var中第 offset 个字符起 length 个字符。 |
- 开头/末尾字符清除
命令格式 | 功能 |
---|---|
${var#pattern} |
从 var 字符串中清除开头一部分匹配 pattern的文本。清除最短的匹配结果 |
${var##pattern} |
清除最长的匹配结果 |
${var%pattern} |
清除的文本从 var 所包含字符串的末尾开始 |
${var%%pattern} |
清除的文本从 var所包含字符串的末尾开始 |
- 查找替换。对 var 的内容执行查找和替换操作。如果找到了匹配通配符 pattern 的文本, 则用 string 的内容替换它。/string 可能会省略掉,这样会导致删除匹配的文本。
命令格式 | 功能 |
---|---|
${var/pattern/string} |
只有第一个匹配项会被替换掉。 |
${var//pattern/string} |
所有的匹配项都会被替换掉。 |
${var/#pattern/string} |
要求匹配项出现在字符串的开头 |
${var/%pattern/string} |
要求匹配项出现在字符串的末尾 |
引用
有时候我们的字符串中包含了可展开的形式,但我们本意不想让这部分展开。通过引用,我们可以禁止一部分字符串的展开。
随着引用程度加强,越来越多的展开被禁止。如果需要禁止所有的展开,我们要使用单引号。
以下例子是无引用,双引号,和单引号的比较结果:
[me@linuxbox ~]$ echo text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER
text /home/me/ls-output.txt a b foo 4 me
[me@linuxbox ~]$ echo "text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER"
text ~/*.txt {a,b} foo 4 me
[me@linuxbox ~]$ echo 'text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER'
text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER
7 函数
Shell 函数有两种语法形式:
function name {
commands
return
}
和
name () {
commands
return
}
通过在变量名之前加上单词 local,来定义局部变量。这就创建了一个只对其所在的 shell 函数起作用的变量。在这个 shell 函数之外,这个变量不再存在。
8 位置参数
shell 提供了一个称为位置参数的变量集合,这个集合包含了命令行中所有独立的单词。
#!/bin/bash
# posit-param: script to view command line parameters
echo "
Number of arguments: $#
$0 = $0
$1 = $1
$2 = $2
$3 = $3
$4 = $4
"
输出结果:
[me@linuxbox ~]$ posit-param a b c d
Number of arguments: 4
$0 = /home/me/bin/posit-param
$1 = a
$2 = b
$3 = c
$4 = d
即使不带命令行参数,位置参数 $0
总会包含命令行中出现的第一个单词,也就是已执行程序的路径名。
实际上通过参数展开方式你可以访问的参数个数多于9个。只要指定一个大于9的数字,用花括号把该数字括起来就可以。 例如 ${10}、 ${55}、 ${211}
等等。
shell 还提供了一个名为 $#
,可以得到命令行参数个数。
正如位置参数被用来给 shell 脚本传递参数一样,它们也能够被用来给 shell 函数传递参数。
shell 提供了两种特殊的参数。他们二者都能扩展成完整的位置参数列表.
参数 | 描述 |
---|---|
$* | 展开成一个从1开始的位置参数列表。当它被用双引号引起来的时候,展开成一个由双引号引起来 的字符串,包含了所有的位置参数,每个位置参数由 shell 变量 IFS 的第一个字符(默认为一个空格)分隔开。 |
$@ | 展开成一个从1开始的位置参数列表。当它被用双引号引起来的时候, 它把每一个位置参数展开成一个由双引号引起来的分开的字符串。 |
“$@” 在大多数情况下是最有用的方法,因为它保留了每一个位置参数的完整性。
9 条件判断
if commands; then
commands
[elif commands; then
commands...]
[else
commands]
fi
经常与 if 一块使用的命令是 test。这个 test 命令执行各种各样的检查与比较。 它有两种等价模式:
test expression
和
[ expression ]
目前的 bash 版本包括一个复合命令,作为加强的 test 命令替代物。它使用以下语法:
[[ expression ]]
这个[[ ]]命令非常 相似于 test 命令(它支持所有的表达式),但是增加了一个重要的新的字符串表达式:
string1 =~ regex
如果 string1匹配扩展的正则表达式 regex,其返回值为真。
10 case语句
#!/bin/bash
# case4-2: test a character
read -n 1 -p "Type a character > "
echo
case $REPLY in
[[:upper:]]) echo "'$REPLY' is upper case." ;;&
[[:lower:]]) echo "'$REPLY' is lower case." ;;&
[[:alpha:]]) echo "'$REPLY' is alphabetic." ;;&
[[:digit:]]) echo "'$REPLY' is a digit." ;;&
[[:graph:]]) echo "'$REPLY' is a visible character." ;;&
[[:punct:]]) echo "'$REPLY' is a punctuation symbol." ;;&
[[:space:]]) echo "'$REPLY' is a whitespace character." ;;&
[[:xdigit:]]) echo "'$REPLY' is a hexadecimal digit." ;;&
esac
- case 语句使用的模式和路径展开中使用的那些是一样的。模式以一个 “)” 为终止符。
- 还可以使用竖线字符作为分隔符,把多个模式结合起来。这就创建了一个 “或” 条件模式。
- 添加的 “;;&” 的语法允许 case 语句继续执行下一条测试,而不是简单地终止运行。
11 循环
- while 语法举例
#!/bin/bash
# while-count: display a series of numbers
count=1
while [ $count -le 5 ]; do
echo $count
count=$((count + 1))
done
echo "Finished."
- util 语法举例
#!/bin/bash
# until-count: display a series of numbers
count=1
until [ $count -gt 5 ]; do
echo $count
count=$((count + 1))
done
echo "Finished."
- for 语法举例
第一种形式:
#!/bin/bash
for i in A B C D; do
echo $i;
done
第二种形式:
#!/bin/bash
# simple_counter : demo of C style for command
for (( i=0; i<5; i=i+1 )); do
echo $i
done