• 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
    
  • 相关阅读:
    div 水平居中 内容居左
    net core 踩坑记录
    正向代理和反向代理
    NOIP2013 | 货车运输
    【转载】字符串Hash & 【题解】好文章
    cqyz oj | 帮助Jimmy | DAG图
    cqyz oj | 猜序列
    转载 | 原码, 反码, 补码 详解
    cqyz oj | 有线电视网
    cqyz oj | 罕见的秩序 | 拓扑排序
  • 原文地址:https://www.cnblogs.com/daozhangblog/p/12446320.html
Copyright © 2020-2023  润新知