薄雾浓云愁永昼,瑞脑消金兽。
佳节又重阳,玉枕纱橱,半夜凉初透东篱把酒黄昏后,有暗香盈袖。
莫道不消魂,帘卷西风,人比黄花瘦。
本文的参考文档主要出自《Linux程序设计》第四版第二章。
可以看做是更符合国人思路的精简版。
本文不会很长,建议全篇阅读。
什么是Shell
shell是人和linux操作系统进行交互的工具。本质上也是一个跑在linux上的程序罢了。除此之外,它还可以执行脚本文件,从文件而不是人这里获取指令。
管道和重定向
重定向输出
一个简单的例子
ls -l > `lsout`.txt
默认情况下,上面这个命令会覆盖lsout.txt
若想在文件上追加
ls -l >> lsout.txt
更多的内容:其实一个程序的输出不止一个。比如还有标准错误输出。(也许你之前没有接触到过,但是这点也很重要)
kill -HUP 1234 >killout.txt 2>killerr.txt
kill如果给一个已经结束的进程发送HUP信号,那么将会报错。
>killout.txt
表示将标准输出重定向到killout.txt这个文件。
2>killerr.txt
表示将标准错误输出定向到killerr.txt这个文件。
2代表的是标准错误输出。
如果将两者都输出到同一个文件,那么可以这样写
kill -HUP 1234 >killout.txt 2>&1
&1
的含义是标准输出的指向。
我们也可以丢弃掉输出的信息:
kill -1 1234 >/dev/null 2>&1
重定向输入
例如:
#more自身也可以直接读取文件
more < killout.txt
管道
有些时候使用管道明显更方便:
ps|sort|more
注意不要这样
cat mydata.txt|sort|uniq>mydata.txt
管道构建时,这些命令是同时进行的,uniq>mydata.txt会清空掉mydata.txt
作为程序设计语言的shell
和python等比较相似,比较简单的shell语句是可以直接在bash上运行的。
例如:
#!/bin/sh
#称之为glob通配符
for file in *
do
#如果在file中找到了'include'
if grep -l include $file
then
echo $file
fi
done
#退出 返回一个有意义的退出码
exit 0
接下来需要把脚本设置为可执行权限:
chmod +x mysh.sh
然后
./mysh.sh
加上.
是因为bash的环境变量PATH中没有当前路径。
shell的语法
变量
shit=shitshit
fuck="fuckfuck"
you=1+3
echo $shit,$fuck,$you
这就是shell的命名方法,创建变量时不需要
,但在获取它的值的时候必须加
。
上面三种变量都是多少?
都是字符串:
shitshit,fuckfuck,1+3
读取输入赋值
read shit
环境变量
变量名 | 说明 |
---|---|
$HOME | 当前用户家目录 |
$PATH | shell用来搜索命令的目录 |
$0 | 当前shell脚本名称 |
$# | 传递给脚本的参数个数 |
$$ | 进程PID |
参数变量
变量名 | 说明 |
---|---|
$1,$2,$3 | 脚本程序的参数 |
$@ | 一个分割开的字符串 |
一个应用实例是:
打印出所有的参数名称:
#!/bin/bash
for i in $@
do
echo $i
done
exit 0
当
./my.sh 1 2 3
1
2
3
条件
判断某一个文件是否存在:
if test -f mysh.sh
then
echo shit you are here
fi
或者这种:
if [ -f mysh.sh ]
then
echo shit
fi
(是不是shell编程很累啊?相比之下python的方式就要友好得多,靠缩进来说明结构,不需要then fi do done这些语句。但是这么做肯定是由当年的一些限制在里边。python要到1989年才能发明出来~)
[ -f mysh.sh ]可以看做test命令的变种。除了进行文件测试外:
还可以有字符串、算术等。具体可以看这里。
控制结构
我们之前已经有提及到其控制结构了:
if condition
then
statements
elif
statements
else
statements
fi
for循环一般是字符串
for variable in values
do
statements
done
但是这样很明显太弱了,for还可以用通配符来拓展:
#!/bin/bash
for file in $(ls f*.sh)
do
#打印文件
lpr $file
done
exit 0
需要特别注意的是在ls f*.sh上加上$()
while语句:
while condition do
statement
done
朴素的密码验证
#!/bin/bash
echo "print your password"
read word
while [ "$word" != "secret" ]
do
echo "Sorry,try again"
read word
done
exit 0
你会注意到$word加了一个引号,这么做是为了防止如下情形:
当word为空时,则变成了[ !=”secret” ]
case语句:
case variable in
pattern[|pattern]...) statements;;
pattern[|pattern]...) statements;;
esac
一个小栗子:
#!/bin/bash
echo "yes or no?(y/n)"
read yn
case "$yn" in
y|yes|ok) echo "confirmed!"
echo "successfully";;
n|no|n*) echo "canceled";;
*) echo "you say what?";;
esac
exit 0
1.注意pattern其实是支持通配符的,*代表了一个字符串
2.注意一个case下可以有多条语句
函数
shell的函数可能看起来不怎么严谨。
function_name(){
statements
}
没有返回类型,没有参数类型。
我们只谈两三个简单的例子:
捕获返回结果
foo(){echo JAY;}
...
result="$(foo)"
一个复杂的点的返回0或1的函数
#!/bin/bash
yesno(){
echo "is your name $* ?"
while true
do
echo -n "Enter yes or no:"
read x
case $x in
y*) return 0;;
n*) return 1;;
*) echo "you say what ?"
esac
done
}
echo "原始参数是: $*"
if yesno "$1"
then
echo "OK"
else
echo "Well..."
fi
exit 0
命令
我认为比较重要的有:
exec命令
exit n命令
expr命令
之前说过,无论是单引号双引号还是都是字符串。
类似于s=1+2
这样得到的结果只是一个字符串1+2
正确的用法是:
x=`expr 1 + 2`
#或者
x=$(expr 1 + 2)
你是否注意到这个$
的作用?我们之前在for的用法中也用到过,如果忘记了请往回看看。
需要注意的是expr是一个命令,后面这个表达式每一个之间都有一个空格,这样命令才能正确识别。
如果我们要实现x的自加
x=$(expr $x + 1)
printf命令
基本形式是:
printf "format string" parameter1 parameter2...
示例
printf "%s %d \t%s" "Hi There" 15 people
“Hi There”之所以也有双引号是因为需要将其看做一个整体。这一技巧还会在很多地方看到。
trap
在Shell编程中比较常用的两个外部命令:
find
find [path] [options] [test] [actions]
find的具体指令请看这里。
另一个是grep
grep [options] PATTERN [FILES]
例如之前我们提到过的
grep -l include *
#搜索当前目录下的所有带有"include"的文件名
具体用法看这里。