Linux中的shell有多种类型,其中最常用的几种是Bourne shell(sh)、C shell(csh)和Korn shell(ksh)。三种shell各有优缺点。 Bourne shell是UNIX最初使用的shell,并且在每种UNIX上都可以使用。Bourne shell在shell编程方面相当优秀,但在处理与用户的交互方面做得不如其他几种shell。Linux操作系统缺省的shell是Bourne Again shell,它是Bourne shell的扩展,简称Bash,与Bourne shell完全向后兼容,并且在Bourne shell的基础上增加、增强了很多特性。Bash放在/bin/bash中,它有许多特色,可以提供如命令补全、命令编辑和命令历史表等功能,它还包含了很多C shell和Korn shell中的优点,有灵活和强大的编程接口,同时又有很友好的用户界面。
可以使用 cat /etc/shells
查看支持的shell类型。我们最常用的就是bash。兼容sh
- 头声明
shell脚本第一行必须以 #!开头,它表示该脚本使用后面的解释器解释执行。
#!/bin/bash
一、shell变量
注意点:
- shell变量 “=”两边不能有空格;
- 合法的标识符(字母、数字、_),不能使用关键字;
- 首字母必须是字;
变量赋值的时候,中间的等于号前后不能有空格。
name=11
echo $name
1name //错误
_name //错误
name = "hello" //错误
- 使用变量
定义过的变量直接使用$来访问这个变量
name="test"
echo $name
echo ${name}
a=z # Assign the string "z" to variable a.
b="a string" # Embedded spaces must be within quotes.
c="a string and $b" # Other expansions such as variables can be
# expanded into the assignment.
d="$(ls -l foo.txt)" # Results of a command.
e=$((5 * 7)) # Arithmetic expansion.
f=" a string
" # Escape sequences such as tabs and newlines.
- 只读变量。
在一个变量的前面加上readonly 表示该变量只读。类似于常量。
readonly PI=3.14
echo $PI
- 删除变量
当一个变量不再使用的时候,可以使用unset删除
name="test"
unset $name
变量的类型。有局部变量、环境变量、shell变量
字符串
字符串和php类似。可以由双引号和单引号括起来,但是双引号括起来的字符串,里面的变量可以解析。
单引号里面不能出现双引号(转义也不可以).所以尽量使用双引号
str="hello''"
str2='hello'
str3='"test"'//错误
str4="str2$str2"
echo $str4
- 字符串拼接
字符串拼接和其他的语言不一样。不需要.也不需要+
name1="hello"
name2="world"
echo $name1 $name2// hello world
- 获取字符串的长度 “#”
str="helloworld"
${#str}
- 字符串切片
使用冒号:
str="helloworld"
echo ${str:0:4} //从0开始截取4个字符 hell
- 字符串判断操作
${var} 变量var的值, 与$var相同
${var-DEFAULT} 如果var没有被声明, 那么就以$DEFAULT作为其值 *
${var:-DEFAULT} 如果var没有被声明, 或者其值为空, 那么就以$DEFAULT作为其值 *
${var=DEFAULT} 如果var没有被声明, 那么就以$DEFAULT作为其值 *
${var:=DEFAULT} 如果var没有被声明, 或者其值为空, 那么就以$DEFAULT作为其值 *
${var+OTHER} 如果var声明了, 那么其值就是$OTHER, 否则就为null字符串
${var:+OTHER} 如果var被设置了, 那么其值就是$OTHER, 否则就为null字符串
${var?ERR_MSG} 如果var没被声明, 那么就打印$ERR_MSG *
${var:?ERR_MSG} 如果var没被设置, 那么就打印$ERR_MSG *
${!varprefix*} 匹配之前所有以varprefix开头进行声明的变量
${!varprefix@} 匹配之前所有以varprefix开头进行声明的变量
- 字符串截取
${#string} $string的长度
${string:position} 在$string中, 从位置$position开始提取子串
${string:position:length} 在$string中, 从位置$position开始提取长度为$length的子串
${string#substring} 从变量$string的开头, 删除最短匹配$substring的子串
${string##substring} 从变量$string的开头, 删除最长匹配$substring的子串
${string%substring} 从变量$string的结尾, 删除最短匹配$substring的子串
${string%%substring} 从变量$string的结尾, 删除最长匹配$substring的子串
${string/substring/replacement} 使用$replacement, 来代替第一个匹配的$substring
${string//substring/replacement} 使用$replacement, 代替所有匹配的$substring
${string/#substring/replacement} 如果$string的前缀匹配$substring, 那么就用$replacement来代替匹配到的$substring
${string/%substring/replacement} 如果$string的后缀匹配$substring, 那么就用$replacement来代替匹配到的$substring
例子
str="hello"
echo ${#str}//5
echo ${str:0:2} //he
echo ${str/l/test}//heltesto
echo ${str//l/test} //hetesttesto
- here document
command << token
text
token
# shell example
cat << _EOF_
<HTML>
<HEAD>
<TITLE>$TITLE</TITLE>
</HEAD>
</HTML>
_EOF_
# terminal example
$ cat << _EOF_
> $foo
> "$foo"
> '$foo'
> $foo
> _EOF_
大括号的作用
$a
${a}
a="foo"
echo "${a}_file" # 和其它字符相连时防止形成不存在的变量
处理空的和不存在的字符
parameter为空或者不存在就用word,存在就用它自己。
${parameter:-word}
和上面基本一样,区别:要赋值。
${parameter:=word}
unset和empty就发发送word到error
${parameter:?word}
${parameter:+word}
String operation(字符串操作符)
${#parameter} # 换成长度
$ foo="This string is long."
$ echo "'$foo' is ${#foo} characters long.
'This string is long.' is 20 characters long
${parameter:offset}
${parameter:offset:length}
[me@linuxbox ~]$ foo="This string is long."
[me@linuxbox ~]$ echo ${foo:5}
string is long.
[me@linuxbox ~]$ echo ${foo:5:6}
string
删除一部分
${parameter#pattern}
${parameter##pattern}
[me@linuxbox ~]$ foo=file.txt.zip
[me@linuxbox ~]$ echo ${foo#*.}
txt.zip
[me@linuxbox ~]$ echo ${foo##*.}
zip
反着删除
${parameter%pattern}
${parameter%%pattern}
[me@linuxbox ~]$ foo=file.txt.zip
[me@linuxbox ~]$ echo ${foo%.*}
file.txt
[me@linuxbox ~]$ echo ${foo%%.*}
file
替换
${parameter/pattern/string}
${parameter//pattern/string}
${parameter/#pattern/string}
${parameter/%pattern/string}
可以用expansion来提高script的效率
大小写转换
可以用来做什么?比如数据库的查找,匹配的时候把输入和数据库中的都统一大小写。
declare
shell不能进行浮点运算
itscs-MacBook-Pro:learnCommandLine itsc$ echo $((3.3+4.2))
-bash: 3.3+4.2: syntax error: invalid arithmetic operator (error token is ".3+4.2")
解决:perl, awk.书里用最简单的bc
为什么连浮点运算都不支持?真是麻烦。
数组
shell数组只支持一维数组。
和php类似。不需要指定数组的大小。
数组用括号抱起来。每个元素用空格分割
arr=(a1 a2 a3)
arr=(1 2 3)
${arr[0]}//1
a[1]=foo
echo ${a[1]}
foo
declare -a a
# 数组申明
name[subscript]=value
name=(value1 value2 ...)
[me@linuxbox ~]$ days=(Sun Mon Tue Wed Thu Fri Sat)
[me@linuxbox ~]$ days=([0]=Sun [1]=Mon [2]=Tue [3]=Wed [4]=Thu [5]=Fri [6]=Sat)
- 获取数组所有的元素
使用@ 或 * 可以获取数组中的所有元素
${arr[*]}
- 获取数组的长度
${#arr[*]}
- 遍历数组
[me@linuxbox ~]$ animals=("a dog" "a cat" "a fish")
[me@linuxbox ~]$ for i in ${animals[*]}; do echo $i; done
[me@linuxbox ~]$ for i in ${animals[@]}; do echo $i; done
[me@linuxbox ~]$ for i in "${animals[*]}"; do echo $i; done
[me@linuxbox ~]$ for i in "${animals[@]}"; do echo $i; done
bash的array不一定是要连续的
所以需要有方法知道哪些位置上有值
[me@linuxbox ~]$ foo=([2]=a [4]=b [6]=c)
[me@linuxbox ~]$ for i in "${foo[@]}"; do echo $i; done
a
b
c
[me@linuxbox ~]$ for i in "${!foo[@]}"; do echo $i; done
2
4
6
- 数组第n个元素的长度
${#arr[2]}
- 数组切片
${arr[*]:0:2} //1 2
- 数组搜索替换
${arr[*]/3/5}
- array的追加
arr=("${arr[*]}" "test")
$ foo=(a b c)
$ foo[100]=e
$ echo ${foo[@]}
a b c e
$ foo+=(k l)
$ echo ${foo[@]}
a b c e k l
$ for i in "${foo[@]}"; do echo $i; done
a
b
c
e
k
l
$ for i in "${!foo[@]}"; do echo $i; done
0
1
2
100
101
102
subscript不是连续的
associative arrays
下标可以是字符
二、运算符
逻辑运算符
&& 逻辑的 AND [[ $a -lt 100 && $b -gt 100 ]] 返回 false
|| 逻辑的 OR [[ $a -lt 100 || $b -gt 100 ]] 返回 true
字符串比较
= 检测两个字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false。
!= 检测两个字符串是否相等,不相等返回 true。 [ $a != $b ] 返回 true。
-z 检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false。
-n 检测字符串长度是否为0,不为0返回 true。 [ -n $a ] 返回 true。
str 检测字符串是否为空,不为空返回 true。 [ $a ] 返回 true。
关系运算符
关系运算符只支持数字
eq 检测两个数是否相等,相等返回 true。 [ $a -eq $b ] 返回 false。
-ne 检测两个数是否相等,不相等返回 true。 [ $a -ne $b ] 返回 true。
-gt 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ] 返回 false。
-lt 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ] 返回 true。
-ge 检测左边的数是否大于等于右边的,如果是,则返回 true。 [ $a -ge $b ] 返回 false。
-le 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ $a -le $b ] 返回 true。
a=10
b=20
if [[ $a eq $b ]];then
echo "等于"
fi
布尔运算符
! 非运算,表达式为 true 则返回 false,否则返回 true。 [ ! false ] 返回 true。
-o 或运算,有一个表达式为 true 则返回 true。 [ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a 与运算,两个表达式都为 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false。
文件测试符号
b file 检测文件是否是块设备文件,如果是,则返回 true。 [ -b $file ] 返回 false。
-c file 检测文件是否是字符设备文件,如果是,则返回 true。 [ -c $file ] 返回 false。
-d file 检测文件是否是目录,如果是,则返回 true。 [ -d $file ] 返回 false。
-f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 [ -f $file ] 返回 true。
-g file 检测文件是否设置了 SGID 位,如果是,则返回 true。 [ -g $file ] 返回 false。
-k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 [ -k $file ] 返回 false。
-p file 检测文件是否是有名管道,如果是,则返回 true。 [ -p $file ] 返回 false。
-u file 检测文件是否设置了 SUID 位,如果是,则返回 true。 [ -u $file ] 返回 false。
-r file 检测文件是否可读,如果是,则返回 true。 [ -r $file ] 返回 true。
-w file 检测文件是否可写,如果是,则返回 true。 [ -w $file ] 返回 true。
-x file 检测文件是否可执行,如果是,则返回 true。 [ -x $file ] 返回 true。
-s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。 [ -s $file ] 返回 true。
-e file 检测文件(包括目录)是否存在,如果是,则返回 true。 [ -e $file ] 返回 true。
file=""
if [[-f $file ]]; then
echo "is a file"
fi
三、流程控制
3.1 if/else
if [condition];then
echo '1'
fi
# if else
if [condition]; then
echo '1'
else
echo '2'
fi
# if elseif else
if [condition];then
elif [condition];then
fi
x=5
if [ "$x" -eq 5 ]; then
echo "x equals 5."
else
echo "x does not equal 5."
fi
3.1.1 test
语法:
test expression
and the more popular:
[ expression ]
expression是true时返回0,否则返回1,test和[ 本质上是一样的。
使用:
#!/bin/bash
# test-file: Evaluate the status of a file
FILE=~/.bashrc
if [ -e "$FILE" ]; then
if [ -f "$FILE" ]; then
echo "$FILE is a regular file."
fi
if [ -d "$FILE" ]; then
echo "$FILE is a directory."
fi
if [ -r "$FILE" ]; then
echo "$FILE is readable."
fi
if [ -w "$FILE" ]; then
echo "$FILE is writable."
fi
if [ -x "$FILE" ]; then
echo "$FILE is executable/searchable."
fi
else
echo "$FILE does not exist"
exit 1
fi
exit # ?
When a script “runs off the end” (reaches end of file), it terminates with an exit status of the last command executed.
String Expressions
#!/bin/bash
# test-string: evaluate the value of a string
ANSWER=maybe
if [ -z "$ANSWER" ]; then
echo "There is no answer." >&2
exit 1
fi
if [ "$ANSWER" = "yes" ]; then
echo "The answer is YES."
elif [ "$ANSWER" = "no" ]; then
echo "The answer is NO."
elif [ "$ANSWER" = "maybe" ]; then
echo "The answer is MAYBE."
else
echo "The answer is UNKNOWN."
Integer Expressions
test的选项真多!!!
test用regex
[[]]
# 检验是不是数
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
(( )) - Designed For Integers
$ if ((1)); then echo "It is true."; fi
It is true.
if ((INT == 0));
if ((INT < 0));
if (( ((INT % 2)) == 0));
Combining Expressions
Operation test [[ ]] and (( ))
AND -a &&
OR -o ||
NOT ! !
例子
# [[]]
if [[ "$INT" -ge "$MIN_VAL" && "$INT" -le "$MAX_VAL" ]];
# test
if [ "$INT" -ge "$MIN_VAL" -a "$INT" -le "$MAX_VAL" ];
Control Operators: Another Way To Branch
The && (AND) and || (OR)
$ mkdir temp && cd temp
$ [[ -d temp ]] || mkdir temp
[]和[[]]的区别
[[]]和[]一样,不过有两个新特性,:
string1 =~ regex
== operator
[[]]和表达式之间要有空格
[[ "$count" -gt 5 ]]
[[ "$count" -gt 5]] # 会报错,执行起来会很可怕
3.2 for
# 两种风格
for variable [in words]; do
commands
done
# c语言的
for (( expression1; expression2; expression3 )); do
commands
done
demo例子
[me@linuxbox ~]$ for i in A B C D; do echo $i; done
itscs-MacBook-Pro:~ itsc$ for i in {A..D}; do echo $i; done
# 文件列表 pathname expansion
itscs-MacBook-Pro:learnCommandLine itsc$ for i in dis*.txt; do echo "$i"; done
3.3 while 与 until
while
语法:
while commands; do commands; done
#!/bin/bash
# while-count: display a series of numbers
count=1
while [[ "$count" -le 5 ]]; do
echo "$count"
count=$((count + 1))
done
echo "Finished."
Breaking Out Of A Loop:break、continue、until
和while相反
count=1
until [[ "$count" -gt 5 ]]; do
echo "$count
count=$((count + 1))
done
echo "Finished.
read file with loop
#!/bin/bash
# while-read
while read distro version release; do
printf "distro: %s version: %s released: %s
"
"$distro"
"$version"
"$release"
done < distros.txt
3.4 case
#!/bin/bash
# case-menu
clear
echo "
please select:
1. display system information
2. display disk space
3. display home space utilization
0. quit
"
read -p "enter selection [0-3] > "
case "$REPLY" in
0) echo "program terminated"
exit
;;
1) echo "hostname: $HOSTNAME"
uptime
;;
2) df -h
;;
3) if [[ "$(id -u)" -eq 0 ]]; then
echo "home space utilization (all users)"
du -sh /home/*
else
echo "home space utilization ($USER)"
du -sh "$HOME"
fi
;;
*) echo "invalid entry" >&2
exit 1
;;
esa
3.5 综合
#! /bin/bash
a=10
b=20
# 判断数值
if [[ $a -ne $b ]]; then
echo "a 不等于b"
fi
# 判断字符串
if [[ '$a' != '$b' ]]; then
echo "1"
fi
# 判断文件
if [[ -d "../doc" ]]; then
echo "dirctory"
fi
if [[ ! -f "../routes" ]]; then
echo "not a file"
fi
#while
while [[ $a -gt 1 ]]; do
#statements
echo $a;
# 条件
let a--
done
# for
for i in "wo" "rds"; do
echo $i
done
四、函数
- 函数 定义如下:[function] functionName(){} ,其中function是可以省略的
function test(){}
# and the simpler (and generally preferred) form:
test(){}
- 函数的调用
函数的调用和其他语言的调用不太一样
function test(){
echo "hello"
}
test #调用函数
- 函数的参数
函数的参数定义不需要在()中定义形参 只需要在调用使用传入即可
$n n代表整数
$1是第一个参数 以此类推
function test(){
echo $1 # 第一个参数 以此类推
}
test 22 //22
- 局部变量 local
Shell函数定义的变量默认是global的,其作用域从“函数被调用时执行变量定义的地方”开始,到shell结束或被显示删除处为止。函数定义的变量可以被显示定义成local的,其作用域局限于函数内。但请注意,函数的参数是local的。
funct_1 () {
local foo # variable foo local to funct_1
foo=1
echo "funct_1: foo = $foo"
}
五、引入外部文件
在shell中有时候需要引入外部的脚本文件 我们需要使用下面的两种方式
- . filename
. ./a.sh
- source filename
在文件中使用source
source ./a.sh
六、命令行接收参数
在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数。
$ bash test.sh test test2
$0 代表脚本文件路径 //test.sh
$1 代表第一个参数 //test
$# 参数的个数 // 2
$* 所有参数
basename 去除文件名前面的字符,只要文件名。
for i in $*; do
echo $i
done
$$ 脚本运行的进程号
$! 最后一个进程号
$? 最后退出的状态 0 表示没有问题
位置参数也可以用在function中
Difference between echo -e “” and echo $“”
e的意思就是扩展,支持转义,e和$支持的符号部分不相同。
∗和*和∗和@的区别
不加双引号时是一样的,遇到空格就拆分,加双引号时不一样,∗会将所有参数放到一个字符串中,*会将所有参数放到一个字符串中,∗会将所有参数放到一个字符串中,@会将每个输入的参数分别当作参数,也就是不区分输入时的空格。
$@经常用。
七、读取键盘输入
read – Read Values From Standard Input
read a single line of standard input.
read [-options] [variable…]
# -n option,suppresses the trailing newline on output
echo -n "Please enter an integer -> "
read int
read var1 var2 var3 var4 var5 # 多个
# -p prompt
read -p "Enter one or more values > "
# -t seconds
# -s Silent mode.
if read -t 10 -sp "Enter secret passphrase > " secret_pass;
IFS
Internal Field Separator
file_info=$(grep "^$user_name:" $FILE)
# 写在一行,IFS只改变跟在后面的 command
IFS=":" read user pw uid gid name home shell <<< "$file_info"
here string
The <<< operator indicates a here string.
You Can’t Pipe read
就像函数与子函数,凡是这一类都不能pip。
Validating Input
Menus
八、特殊
讲一些不常用的,在特定场景下使用的
Group Commands And Subshells
Group command:
{ command1; command2; [command3; ...] }
Subshell:
(command1; command2; [command3;...])
它们是做什么的?
manage redirection
{ ls -l; echo "Listing of foo.txt"; cat foo.txt; } > output.txt
(ls -l; echo "Listing of foo.txt"; cat foo.txt) > output.txt
结合pip
{ ls -l; echo "Listing of foo.txt"; cat foo.txt; } | lpr
注意大括号的使用
due to the way bash implements
group commands, the braces must be separated from the commands by a space and the
last command must be terminated with either a semicolon or a newline prior to the closing brace.
group和subshell的区别
subshell和它的名字一样,返回时enviroment会丢失,所以一般情况下用group.
echo "foo" | read
echo $REPLY # 这就是subshell的例子,reply是空的
commands in pipelines are always executed in subshells
process substitution
用来解决subshell的问题
read <<(echo "foo")
echo $REPLY