总结自:
https://github.com/qinjx/30min_guides/blob/master/shell.md;
http://blog.itpub.net/14293828/viewspace-1447570
看个例子吧:php
#!/bin/sh cd ~ mkdir shell_tut cd shell_tut for ((i=0; i<10; i++)); do touch test_$i.txt done
cd, mkdir, touch都是系统自带的程序,通常在/bin或者/usr/bin目录下。for, do, done是sh脚本语言的关键字。html
shell是指一种应用程序,这个应用程序提供了一个界面,用户经过这个界面访问操做系统内核的服务。Ken Thompson的sh是第一种Unix Shell,Windows Explorer是一个典型的图形界面Shell。java
shell脚本(shell script),是一种为shell编写的脚本程序。业界所说的shell一般都是指shell脚本,但读者朋友要知道,shell和shell script是两个不一样的概念。因为习惯的缘由,简洁起见,本文出现的“shell编程”都是指shell脚本编程,不是指开发shell自身(如Windows Explorer扩展开发)。python
shell编程跟java、php编程同样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就能够了。linux
当前主流的操做系统都支持shell编程,本文档所述的shell编程是指Linux下的shell,讲的基本都是POSIX标准下的功能,因此,也适用于Unix及BSD(如Mac OS)。ios
Linux默认安装就带了shell解释器。git
Mac OS不只带了sh、bash这两个最基础的解释器,还内置了ksh、csh、zsh等不经常使用的解释器。github
windows出厂时没有内置shell解释器,须要自行安装,为了同时能用grep, awk, curl等工具,最好装一个cygwin或者mingw来模拟linux环境。web
即Bourne shell,POSIX(Portable Operating System Interface)标准的shell解释器,它的二进制文件路径一般是/bin/sh,由Bell Labs开发。正则表达式
本文讲的是sh,若是你使用其它语言用做shell编程,请自行参考相应语言的文档。
Bash是Bourne shell的替代品,属GNU Project,二进制文件路径一般是/bin/bash。业界一般混用bash、sh、和shell,好比你会常常在招聘运维工程师的文案中见到:熟悉Linux Bash编程,精通Shell编程。
在CentOS里,/bin/sh是一个指向/bin/bash的符号连接:
[root@centosraw ~]# ls -l /bin/*sh -rwxr-xr-x. 1 root root 903272 Feb 22 05:09 /bin/bash -rwxr-xr-x. 1 root root 106216 Oct 17 2012 /bin/dash lrwxrwxrwx. 1 root root 4 Mar 22 10:22 /bin/sh -> bash
但在Mac OS上不是,/bin/sh和/bin/bash是两个不一样的文件,尽管它们的大小只相差100字节左右:
iMac:~ wuxiao$ ls -l /bin/*sh -r-xr-xr-x 1 root wheel 1371648 6 Nov 16:52 /bin/bash -rwxr-xr-x 2 root wheel 772992 6 Nov 16:52 /bin/csh -r-xr-xr-x 1 root wheel 2180736 6 Nov 16:52 /bin/ksh -r-xr-xr-x 1 root wheel 1371712 6 Nov 16:52 /bin/sh -rwxr-xr-x 2 root wheel 772992 6 Nov 16:52 /bin/tcsh -rwxr-xr-x 1 root wheel 1103984 6 Nov 16:52 /bin/zsh
理论上讲,只要一门语言提供了解释器(而不只是编译器),这门语言就能够胜任脚本编程,常见的解释型语言都是能够用做脚本编程的,如:Perl、Tcl、Python、PHP、Ruby。Perl是最老牌的脚本编程语言了,Python这些年也成了一些linux发行版的预置解释器。
编译型语言,只要有解释器,也能够用做脚本编程,如C shell是内置的(/bin/csh),Java有第三方解释器Jshell,Ada有收费的解释器AdaScript。
以下是一个PHP Shell Script示例(假设文件名叫test.php):
#!/usr/bin/php <?php for ($i=0; $i < 10; $i++) echo $i . "\n"; 执行: /usr/bin/php test.php 或者: chmod +x test.php ./test.php
若是你已经掌握了一门编程语言(如PHP、Python、Java、JavaScript),建议你就直接使用这门语言编写脚本程序,虽然某些地方会有点啰嗦,但你能利用在这门语言领域里的经验(单元测试、单步调试、IDE、第三方类库)。
新增的学习成本很小,只要学会怎么使用shell解释器(Jshell、AdaScript)就能够了。
若是你以为本身熟悉的语言(如Java、C)写shell脚本实在太啰嗦,你只是想作一些备份文件、安装软件、下载数据之类的事情,学着使用sh,bash会是一个好主意。
shell只定义了一个很是简单的编程语言,因此,若是你的脚本程序复杂度较高,或者要操做的数据结构比较复杂,那么仍是应该使用Python、Perl这样的脚本语言,或者是你原本就已经很擅长的高级语言。由于sh和bash在这方面很弱,好比说:
若是你的脚本是提供给别的用户使用,使用sh或者bash,你的脚本将具备最好的环境兼容性,perl很早就是linux标配了,python这些年也成了一些linux发行版的标配,至于mac os,它默认安装了perl、python、ruby、php、java等主流编程语言。
在shell脚本中可使用三类命令:
1)Unix 命令:
虽然在shell脚本中可使用任意的unix命令,可是仍是由一些相对更经常使用的命令。这些命令一般是用来进行文件和文字操做的。
经常使用命令语法及功能:
echo "some text": 将文字内容打印在屏幕上。 ls: 文件列表。 wc –l file wc -w file wc -c file: 计算文件行数 计算文件中的单词数 计算文件中的字符数。 cp sourcefile destfile: 文件拷贝。 mv oldname newname : 重命名文件或移动文件。 rm file: 删除文件。 grep 'pattern' file: 在文件内搜索字符串好比:grep 'searchstring' file.txt cut -b colnum file: 指定欲显示的文件内容范围,并将它们输出到标准输出设备好比:输出每行第5个到第9个字符cut –b 5-9 file.txt千万不要和cat命令混淆,这是两个彻底不一样的命令。 cat file.txt: 输出文件内容到标准输出设备(屏幕)上。 file somefile: 获得文件类型。 read var: 提示用户输入,并将输入赋值给变量。 sort file.txt: 对file.txt文件中的行进行排序。 uniq: 删除文本文件中出现的行列好比: sort file.txt | uniq。 expr: 进行数学运算Example: add 2 and 3 expr 2 "+" 3。 find: 搜索文件好比:根据文件名搜索find . -name filename -print。 tee: 将数据输出到标准输出设备(屏幕) 和文件好比:somecommand | tee outfile。 basename file: 返回不包含路径的文件名好比: basename /bin/tux将返回 tux。 dirname file: 返回文件所在路径好比:dirname /bin/tux将返回 /bin。 head file: 打印文本文件开头几行。 tail file : 打印文本文件末尾几行。 sed: Sed是一个基本的查找替换程序。能够从标准输入(好比命令管道)读入文本,并将结果输出到标准输出(屏幕)。该命令采用正则表达式(见参考)进行搜索。不要和shell中的通配符相混淆。好比:将linuxfocus 替换为 LinuxFocus :cat text.file | sed 's/linuxfocus/LinuxFocus/' > newtext.file。 awk: awk 用来从文本文件中提取字段。缺省地,字段分割符是空格,可使用-F指定其余分割符。cat file.txt | awk -F, '{print "," }'这里咱们使用,做为字段分割符,同时打印第一个和第三个字段。若是该文件内容以下: Adam Bor, 34, IndiaKerry Miller, 22, USA 命令输出结果为: Adam Bor, IndiaKerry Miller.
2) 概念: 管道, 重定向和 backtick
这些不是系统命令,可是他们真的很重要。
管道 (|) 将一个命令的输出做为另一个命令的输入。
grep "hello" file.txt | wc -l |
在file.txt中搜索包含有”hello”的行并计算其行数。在这里grep命令的输出做为wc命令的输入。固然您可使用多个命令。
重定向:将命令的结果输出到文件,而不是标准输出(屏幕)。
> 写入文件并覆盖旧文件。
>> 加到文件的尾部,保留旧文件内容。
反短斜线,使用反短斜线能够将一个命令的输出做为另一个命令的一个命令行参数。
命令:
find . -mtime -1 -type f -print |
用来查找过去24小时(-mtime –2则表示过去48小时)内修改过的文件。若是您想将全部查找到的文件打一个包,则可使用如下脚本:
#!/bin/sh # The ticks are backticks (`) not normal quotes ('): tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print` |
3) 流程控制
"if" 表达式 若是条件为真则执行then后面的部分:
if ....; then
打开文本编辑器,新建一个文件,扩展名为sh(sh表明shell),扩展名并不影响脚本执行,见名知意就好,若是你用php写shell 脚本,扩展名就用php好了。
输入一些代码,第一行通常是这样:
#!/bin/bash
#!/usr/bin/php
“#!”是一个约定的标记,它告诉系统这个脚本须要什么解释器来执行。
运行Shell脚本有两种方法:
chmod +x test.sh ./test.sh
注意,必定要写成./test.sh,而不是test.sh,运行其它二进制的程序也同样,直接写test.sh,linux系统会去PATH里寻找有没有叫test.sh的,而只有/bin, /sbin, /usr/bin,/usr/sbin等在PATH里,你的当前目录一般不在PATH里,因此写成test.sh是会找不到命令的,要用./test.sh告诉系统说,就在当前目录找。
经过这种方式运行bash脚本,第一行必定要写对,好让系统查找到正确的解释器。
这里的"系统",其实就是shell这个应用程序(想象一下Windows Explorer),但我故意写成系统,是方便理解,既然这个系统就是指shell,那么一个使用/bin/sh做为解释器的脚本是否是能够省去第一行呢?是的。
这种运行方式是,直接运行解释器,其参数就是shell脚本的文件名,如:
/bin/sh test.sh /bin/php test.php
这种方式运行的脚本,不须要在第一行指定解释器信息,写了也没用。
定义变量时,变量名不加美圆符号($),如:
your_name="qinjx"
注意,变量名和等号之间不能有空格,这可能和你熟悉的全部编程语言都不同。
除了显式地直接赋值,还能够用语句给变量赋值,如:
for file in `ls /etc`
使用一个定义过的变量,只要在变量名前面加美圆符号便可,如:
your_name="qinjx" echo $your_name echo ${your_name}
变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,好比下面这种状况:
for skill in Ada Coffe Action Java do echo "I am good at ${skill}Script" done
若是不给skill变量加花括号,写成echo "I am good at $skillScript",解释器就会把$skillScript当成一个变量(其值为空),代码执行结果就不是咱们指望的样子了。
推荐给全部变量加上花括号,这是个好的编程习惯。IntelliJ IDEA编写shell script时,IDE就会提示加花括号。
已定义的变量,能够被从新定义,如:
your_name="qinjx" echo $your_name your_name="alibaba" echo $your_name
这样写是合法的,但注意,第二次赋值的时候不能写$your_name="alibaba",使用变量的时候才加美圆符。
以“#”开头的行就是注释,会被解释器忽略。
sh里没有多行注释,只能每一行加一个#号。就像这样:
#-------------------------------------------- # 这是一个自动打ipa的脚本,基于webfrogs的ipa-build书写:https://github.com/webfrogs/xcode_shell/blob/master/ipa-build # 功能:自动为etao ios app打包,产出物为14个渠道的ipa包 # 特点:全自动打包,不须要输入任何参数 #-------------------------------------------- ##### 用户配置区 开始 ##### # # # 项目根目录,推荐将此脚本放在项目的根目录,这里就不用改了 # 应用名,确保和Xcode里Product下的target_name.app名字一致 # ##### 用户配置区 结束 #####
若是在开发过程当中,遇到大段的代码须要临时注释起来,过一下子又取消注释,怎么办呢?每一行加个#符号太费力了,能够把这一段要注释的代码用一对花括号括起来,定义成一个函数,没有地方调用这个函数,这块代码就不会执行,达到了和注释同样的效果。
字符串是shell编程中最经常使用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了,哈哈),字符串能够用单引号,也能够用双引号,也能够不用引号。单双引号的区别跟PHP相似。
str='this is a string'
单引号字符串的限制:
your_name='qinjx' str="Hello, I know your are \"$your_name\"! \n"
your_name="qinjx" greeting="hello, "$your_name" !" greeting_1="hello, ${your_name} !" echo $greeting $greeting_1
string="abcd" echo ${#string} #输出:4
string="alibaba is a great company" echo ${string:1:4} #输出:liba
string="alibaba is a great company" echo `expr index "$string" is`#输出:8,这个语句的意思是:找出单词is在这名话中的位置
参见本文档末尾的参考资料中Advanced Bash-Scripting Guid Chapter 10.1
和Java、PHP等语言不同,sh的流程控制不可为空,如:
<?php if (isset($_GET["q"])) { search(q); } else { //do nothing }
在sh/bash里可不能这么写,若是else分支没有语句执行,就不要写这个else。
还要注意,sh里的if [ $foo -eq 0 ],这个方括号跟Java/PHP里if后面的圆括号大不相同,它是一个可执行程序(和cd, ls, grep同样),相不到吧?在CentOS上,它在/usr/bin目录下:
ll /usr/bin/[ -rwxr-xr-x. 1 root root 33408 6月 22 2012 /usr/bin/[
正由于方括号在这里是一个可执行程序,方括号后面必须加空格,不能写成if [$foo -eq 0]
if condition then command1 command2 ... commandN fi
写成一行(适用于终端命令提示符):
if `ps -ef | grep ssh`; then echo hello; fi
末尾的fi就是if倒过来拼写,后面还会遇到相似的
if condition then command1 command2 ... commandN else command fi
if condition1 then command1 elif condition2 command2 else commandN fi
在开篇的示例里演示过了:
for var in item1 item2 ... itemN do command1 command2 ... commandN done
写成一行:
for var in item1 item2 ... itemN; do command1; command2… done;
for (( EXP1; EXP2; EXP3 )) do command1 command2 command3 done
while condition do command done
while : do command done
或者
while true do command done
或者
for (( ; ; ))
until condition do command done
case "${opt}" in "Install-Puppet-Server" ) install_master $1 exit ;; "Install-Puppet-Client" ) install_client $1 exit ;; "Config-Puppet-Server" ) config_puppet_master exit ;; "Config-Puppet-Client" ) config_puppet_client exit ;; "Exit" ) exit ;; * ) echo "Bad option, please choose again" esac
case的语法和C family语言差异很大,它须要一个esac(就是case反过来)做为结束标记,每一个case分支用右圆括号,用两个分号表示break
可使用source和.关键字,如:
source ./function.sh . ./function.sh
在bash里,source和.是等效的,他们都是读入function.sh的内容并执行其内容(相似PHP里的include),为了更好的可移植性,推荐使用第二种写法。
包含一个文件和在执行一个文件同样,也要写这个文件的路径,不能光写文件名,好比上述例子中:
. ./function.sh
不能够写做:
. function.sh
若是function.sh是用户传入的参数,如何得到它的绝对路径呢?方法是:
real_path=`readlink -f $1`#$1是用户输入的参数,如function.sh . $real_path