shell脚本之入门

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变量

注意点:

  1. shell变量 “=”两边不能有空格;
  2. 合法的标识符(字母、数字、_),不能使用关键字;
  3. 首字母必须是字;

变量赋值的时候,中间的等于号前后不能有空格

name=11
echo $name
1name //错误
_name //错误
name = "hello" //错误
  1. 使用变量

定义过的变量直接使用$来访问这个变量

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.
  1. 只读变量。

在一个变量的前面加上readonly 表示该变量只读。类似于常量。

readonly PI=3.14
echo $PI
  1. 删除变量

当一个变量不再使用的时候,可以使用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中有时候需要引入外部的脚本文件 我们需要使用下面的两种方式

  1. . filename
. ./a.sh
  1. 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
原文地址:https://www.cnblogs.com/daozhangblog/p/12446320.html