• 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
    
  • 相关阅读:
    Path Sum II
    Convert Sorted Array to Binary Search Tree
    Construct Binary Tree from Inorder and Postorder Traversal
    Construct Binary Tree from Preorder and Inorder Traversal
    Maximum Depth of Binary Tree
    Binary Tree Zigzag Level Order Traversal
    Binary Tree Level Order Traversal
    Same Tree
    Validate Binary Search Tree
    Binary Tree Inorder Traversal
  • 原文地址:https://www.cnblogs.com/daozhangblog/p/12446320.html
Copyright © 2020-2023  润新知