当前位置: 首页 > 工具软件 > linux-command > 使用案例 >

linux命令行入门(七) 《The Linux Command Line》 终

伊裕
2023-12-01

第三十一章31-疑难排解
    随着脚本越来越复杂,出故障的概率也越来越大,
    本章学习几个可以跟踪和消除问题的有用技巧。

31.1    语法错误
            出现语法错误,绝大多数情况,shell会拒绝执行脚本
            1.丢失引号,使用vi编辑器语法高亮帮助查找错误,:syntax on
            2.丢失或意外的标记, 比如说丢失if后的分号,
                if 会计算列表中最后一个命令的退出代码
            3.意料不到的展开,例如  [$num = 1] 区别于 ["$num" = 1]
                当$num 里面什么都没有时,前者会报语法错误。

31.2    逻辑错误 
            脚本会正常执行,但是不会得到期望的结果
            1.不正确的条件表达式,例如 if xxx,逻辑颠倒等
            2.循环边界问题,循环语句常犯错误,过早结束循环或循环过多
            3.意外情况,大多数逻辑错误来自意料之外的情况
            防范措施:
            1. cd $dir_name && rm *  可能的意外:dir_name变量值为空
            2. [[-d $dir_name]] && cd $dir_name && rm * 
                持续改进:对于意外的情况,最好终止执行,提示错误信息
                echo "wrong +10086" >&2
            3. 验证用户输入,例如 [[$REPLY =~ ^[0-3]$ ]]

31.3    测试
            行业经验,早发布,常发布,早期修复bug,成本低
            1.好的代码让测试更简单,使用echo 命令与注释,标记测试的改动
            2.测试案例要全,比如 两数比较大小有3种情况
            3.重要的功能花在测试的时间要多些

            if [[ -d $dir_name ]]; then
                if cd $dir_name; then
                    echo rm * # TESTING
                else
                    echo "cannot cd to '$dir_name'" >&2
                    exit 1
                fi
            else
                echo "no such directory: '$dir_name'" >&2
                exit 1
            fi
            exit # TESTING    

31.4    调试
            设计良好的脚本应该具备防卫能力,能检测异常发生,给用户反馈。
            1.我们可以注释代码块,让错误隔离在一个区域
            2.标准错误输出>&2,看看执行顺序和时间是否正确。
            3.脚本第一行#!/bin/xxx,末尾加上 -x 选项 ,进行全脚本追踪
            4.在-x基础上,使用PS4操作符可以追踪显示行号
            5.使用set -x [set +x] 命令 进行选定区域 追踪脚本 
            6.循环内部,打印变量数值往往可以发现问题原因
                       
            [seven@localhost playground]$ bash 31.1.txt 
            number=1
            + '[' 1 = 1 ']'
            + echo 'Number is equal to 1.'
            Number is equal to 1.
            + set +x

            [seven@localhost playground]$ export PS4='$LINENO + '
            [seven@localhost playground]$ bash 31.1.txt
            6 + '[' 1 = 1 ']'
            7 + echo 'Number is equal to 1.'
            Number is equal to 1.
            11 + set +x


第三十二章32-流程控制(case分支)
    在28章我们学了if、elif语句,但是elif还不够方便,
    许多编程语言额外增加了
    类似于switch、case之类的流程控制机制。

32.1    case
            Bash的多选复合命令称为case,语法为:
                case word in
                    [pattern [|pattern]...] commands ;;]...
                esac
            特定是,case命令检测一个变量值,试图匹配其中一个具体的模式,
            当与之相匹配的模式找到之后,就会执行与改模式相关联的命令。
            若找到一个模式之后,就不会再继续寻找,模式以一个‘)’为终止符。
                脚本例子:
                #!/bin/bash
                echo "
                please select:
                1. Display System Information
                0. Quit
                "
                read -p "Enter selection [0-1] >"
                case $REPLY in
                    0) echo "Program terminated."
                        exit
                        ;;
                    1) echo "Hostname: $HOSTNAME"
                        uptime        
                        ;;
                    *) echo "Invalid entry" >&2
                        exit
                        ;;
                    esac

        实验例子:
                Enter selection [0-1] >1
                Hostname: localhost.localdomain
                 10:42:19 up 2 days,  6:41,  4 users,  load average: 0.00, 0.06, 0.06
                [seven@localhost playground]$ bash 32.1.TXT

                please select:
                1. Display System Information
                0. Quit

                Enter selection [0-1] >0
                Program terminated.
                [seven@localhost playground]$ bash 32.1.TXT

                please select:
                1. Display System Information
                0. Quit

                Enter selection [0-1] >3
                Invalid entry


32.2    模式
            上面的case例子中,模式以一个‘)’为终止符,除了常见的外,还有冷门的。
            如:[[:alpha:]] 描述 若单词是一个字母字符
                ???)        描述 若单词只有3个字符
                *.txt       描述 若单词以 ‘.txt’ 字符结尾
                *)          描述 匹配任意单词,相当于末尾的else
                脚本例子:
                #!/bin/bash
                read -p "enter word > "
                case $REPLY in
                    [[:alpha:]])     echo "is a single alphabetic character." ;;
                    [ABC][0-9])     echo "is A, B, or C followed by a digit." ;;
                    ???)             echo "is three characters long." ;;
                    *.txt)         echo "is a word ending in '.txt'" ;;
                    *)             echo "is something else." ;;
                esac
        实验例子:
                [seven@localhost playground]$ bash 32.1.TXT
                please select:
                1. Display System Information
                0. Quit

                Enter selection [0-1] >3
                Invalid entry
                [seven@localhost playground]$ bash 32.2.txt
                enter word > 666.txt
                is a word ending in '.txt' 

32.3    执行多个动作
            这是一个新特性,在bash4.0以前,case只能匹配一个模式,然后终止。
            bash4.0以后 末尾使用 ;;& 可以让代码继续传递下去,
            类似于 c语言的switch用法

第三十三期33-位置参数
    位置参数是 shell特有的东西,首先它是一个参数集合,
    集合中包含了命令行中所有独立的单词。
    学习它的意义在于,在此之前的程序还缺少接受和处理参数的能力。
    本章将学习让程序访问命令行内容。

33.1    访问命令行
            位置参数的变量集合中的变量按照从0-9给予命名。也可以大于9,例如: ${10}
                [seven@localhost playground]$ bash 33.1.a.txt
                $0 = 33.1.a.txt
                $1 = 
                $2 = 

            1.确定参数个数: $#   这个变量代表参数个数
                参数会从$1开始算
                [seven@localhost playground]$ bash 33.txt abq.iop
                Number of arguments: 1
                $0 = 33.txt
                $1 = abq.iop
                $2 = 
            
            2.shift-遍历神器:每次执行shift命令,变量$2移动到$1,以此类推。
                与此同时变量$#的值也会相应减1,注意$0的值永不变。
                
            3.简单应用:这里不使用shift命令,我们仅仅是输出,当前文件信息。
                    这里 PROGNAME 就是 basename $0 命令的执行结果,清除路径开头。
                        故 $1 = PROGNAME = $(basename $0)
                #!/bin/bash
                # file_info: simple file information program
                PROGNAME=$(basename $0)
                if [[ -e $1 ]]; then
                    echo -e "\nFile Type:"
                    file $1
                    echo -e "\nFile Status:"
                    stat $1
                else
                    echo "$PROGNAME:usage:$PROGNAME file" >&2
                    exit 1
                fi

            4.Shell 函数中使用位置参数 
                FUNCNAME变量将由shell自动更新,以便跟踪当前执行的shell函数。
                调用下面这个函数,注意是脚本调用函数,需要带上一个文件名参数。
                    
                file_info () {
                # file_info: function to display file information
                if [[ -e $1 ]]; then
                    echo -e "\nFile Type:"
                    file $1
                else
                    echo "$FUNCNAME: usage: $FUNCNAME file" >&2
                    return 1
                fi
                }

33.2    处理集体位置参数
            shell提供了四种不同的得到位置参数列表的方法,但是目前为止,("$@") 是最好用的。
                因为它保留了每一个位置参数的完整性。

33.3    一个更复杂的应用
            我们将加强前面的 sys_info_page , 添加 输出文件(-f file)、交互模式(-i)、帮助(-h)
            
                usage () {
                    echo "$PROGNAME: usage: $PROGNAME [-f file | -i]"
                    return
                }
                # process command line options
                interactive=
                filename=
                while [[ -n $1 ]]; do
                    case $1 in
                    -f | --file) shift
                                filename=$1
                                ;;
                    -i | --interactive) interactive=1
                                ;;
                    -h | --help) usage
                                exit
                                ;;
                    *) usage >&2
                                exit 1
                                ;;
                    esac
                    shift
                done

            这里省略一个100行的程序,看不懂。 

第三十四章34-流程控制(for循环)
    本章是流程控制的最后一章。本章学习for循环。
    现代版的bash,有两种for循环的格式,任君选择。

34.1    for:传统shell格式
            for variable [in words]; do
                commands
            done
            可以使用{A..Z} 创建words列表
            实验:[seven@localhost playground]$ for i in A B C;do echo $i; done
                    A
                    B
                    C

34.2    C语言格式
            for (( expression1 ))
            while (( expression2 ));do
                commands
                (( expression3 ))
            done                

            实验:[seven@localhost playground]$ for ((i=0 ;i<2;i++));
                    > do echo $i
                    > done
                    0
                    1
                    
34.3    总结
            report_home_space 函数加大难度,在练习中复习命令。

            report_home_space () {
                if [[ $(id -u) -eq 0 ]]; then # admin
                    cat <<- _EOF_
                    <H2>Home Space Utilization (All Users)</H2>
                    <PRE>$(du -sh /home/*)</PRE>
                    _EOF_
                else
                    cat <<- _EOF_
                    <H2>Home Space Utilization ($USER)</H2>
                    <PRE>$(du -sh $HOME)</PRE>
                    _EOF_
                fi
                return
            }
            继续加大难度
            report_home_space () {
                local format="%8s%10s%10s\n"
                local i dir_list total_files total_dirs total_size user_name
                if [[ $(id -u) -eq 0 ]]; then # admin
                    dir_list=/home/*
                    user_name="All Users"
                else
                    dir_list=$HOME
                    user_name=$USER
                fi
                echo "<H2>Home Space Utilization ($user_name)</H2>"
                for i in $dir_list; do
                    total_files=$(find $i -type f | wc -l)
                    total_dirs=$(find $i -type d | wc -l)
                    total_size=$(du -sh $i | cut -f 1)
                    echo "<H3>$i</H3>"
                    echo "<PRE>"
                    printf "$format" "Dirs" "Files" "Size"
                    printf "$format" "----" "-----" "----"
                    printf "$format" $total_dirs $total_files $total_size
                    echo "</PRE>"
                done
                return
            }
            以上内容用到了,目前为止我们学过的许多知识。

第三十五章35-字符串和数字
    之前学了文件的读写操作,本章学更细致的,比如说处理字符串和数字。
    再后面的学习中,使用bc,完成高级的数学运算。

35.1    参数展开
            大多数的参数展开会出现在脚本中,我们也曾经使用过一些形式的参数展开。
                例如,shell变量,echo $HOME
            1.基本参数:最基本的参数展开形式就是${a},这样就变成a所包含的值。                
                        稍微复杂点的形式是 echo "${a}_file",变成字符串拼接。
                        
            2.管理空变量的展开
                如果对一个变量展开后发现是空变量,这就很恶心了,
                    我们希望有一个默认值,而不是光秃秃的的空变量。
                    ${parameter:-word}, 默认值填在word处。
                    当parameter展开为空时,默认值就会起作用。
                如果我们要求很严格,发现空变量就要报告发生错误退出,
                    ${parameter:?"some word to send!!!"}
                    当parameter展开为空时,word的内容就会发送到标准错误。
                如果我们想搞事情,发现空变量不管,如果不为空就替换。
                    ${parameter:+"word you want substitute!!"}
                    当parameter展开不为空时,发生变量替换。    

35.2    返回变量名的参数展开
            shell具有返回变量名的能力,这个功能会用在相当独特的环境。
            ${!prefix*}
            ${!prefix@}
            这种展开会返回以prefix开头的已有变量名。

            1.字符串展开
                ${#parameter}
                    经典案例,路径名的展开。
                    [seven@localhost ~]$ foo="This string is long."
                    [seven@localhost ~]$ echo "'$foo' is ${#foo} characters long."
                    'This string is long.' is 20 characters long.
                ${parameter:offset:length}
                    子串的提取,始于第offset字符,长度为length
                    [seven@localhost ~]$ echo ${foo:5}
                    string is long.
                    [seven@localhost ~]$ echo ${foo:5:6}
                    string
                    如果offset是负数,表示字符串倒数第n个位置开始。
                ${parameter#pattern}
                ${parameter##pattern}
                    这些展开会从paramter中删除一部分,通过匹配的文本。
                    # 表示清除最短的匹配结果,##表示清除最长的匹配结果
                    [seven@localhost ~]$ foo=file.txt.zip
                    [seven@localhost ~]$ echo ${foo#*.}
                    txt.zip
                    [seven@localhost ~]$ echo ${foo##*.}
                    zip
                ${parameter%pattern}
                ${parameter%%pattern}
                    与#和##类型,区别是它们清除的文本从末尾开始,而不是开头
                    [seven@localhost ~]$ foo=file.txt.zip
                    [seven@localhost ~]$ echo ${foo%.*}
                    file.txt
                    [seven@localhost ~]$ echo ${foo%%.*}
                    file
                ${parameter/pattern/string} 只替换第一个
                ${parameter//pattern/string} 替换全部
                ${parameter/#pattern/string} 匹配项出现在串的开头
                ${parameter/%pattern/string} 匹配项出现在串的末尾  

                这个脚本的功能是 查找最长的字符串长度
                难点梳理:
                    这里出现了简写,用参数展开 ${#j} 取代命令 $(echo $j | wc -c) 
                    strings 表示 string数组
                    看不懂最外面那层的for,干哈用。    
                #!/bin/bash
                # longest-word3 : find longest string in a file
                for i; do
                    if [[ -r $i ]]; then
                    max_word=
                    max_len=
                        for j in $(strings $i); do
                            len=${#j}
                            if (( len > max_len )); then
                            max_len=$len
                            max_word=$j
                            fi
                        done
                        echo "$i: '$max_word' ($max_len characters)"
                    fi
                    shift
                done

            2.大小写转换
                这个功能除了提高审美价值,还有就是规范化用户输入。
                [[$1]] 是一个字符串变量
                echo ${1,,} 表示全部字母转成小写
                echo ${1,} 表示仅仅把第一个字符转成小写
                echo ${1^^} 表示全部字母转成大写字母
                echo ${1^} 表示仅仅把第一个字符转成大写字母

                #!/bin/bash
                # ul-param - demonstrate case conversion via parameter expansion
                if [[ $1 ]]; then
                    echo ${1,,}
                    echo ${1,}
                    echo ${1^^}
                    echo ${1^}
                fi
                实验例子:
                    [seven@localhost playground]$ bash 35.2.txt aBc
                    abc
                    aBc
                    ABC
                    ABc
           
35.3    算术求值和展开
            前面的第七章学过算术展开,$((expression)),但是它对小数无能为力。

            1.复习一下,复合命令(()),此命令用做算术求值(真测试) 

            2.复习一下,第九章中我们发现shell支持任意进制的任意整形常量。
                number :默认为10进制数,以10为底
                0number:被认为是八进制数
                0xnumber:十六进制表示法
                base#number:以base为底的数
                实验例子:
                    [seven@localhost playground]$ echo $((0xff))
                    255
                    [seven@localhost playground]$ echo $((2#11111111))
                    255

            3.位运算,~(按位取反),《(左移变大),》(右移变小)
            
            4.逻辑运算符
                逻辑运算符有好多,类似于小于,大于,不小于,就不多说了。
                特别提一下,三目运算符中的 +=或-=得加上括号,否则报错。
                                        
35.4    bc(一种高精度计时器语言)
            整形算术很强大,但是如果想用浮点数,就歇菜了,至少shell歇菜了,
            shell脚本得求助使用bc脚本。
            通过 bc < xx符号可以把xx命令的标准输出给 bc
            通过 bc <<< "2+2"  可以使用here字符串传递脚本给bc

            1. 使用bc的案例:
                [seven@localhost playground]$ bc 35.4.1.txt
                bc 1.06.95
                Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
                This is free software with ABSOLUTELY NO WARRANTY.
                For details type warranty. 
                4
                quit
                [seven@localhost playground]$ bc -q
                2 + 2
                4
                quit
                [seven@localhost playground]$ bc < 35.4.1.txt
                4
                [seven@localhost playground]$ bc <<< "2+2"
                4

            2. 计算每月的还贷金额
                #!/bin/bash
                # loan-calc : script to calculate monthly loan payments
                PROGNAME=$(basename $0)
                usage () {
                    cat <<- EOF
                    Usage: $PROGNAME PRINCIPAL INTEREST MONTHS
                    Where:
                    PRINCIPAL is the amount of the loan.
                    INTEREST is the APR as a number (7% = 0.07).
                    MONTHS is the length of the loan's term.
                    EOF
                }
                if (($# != 3)); then
                    usage
                    exit 1
                fi
                principal=$1
                interest=$2
                months=$3
                bc <<- EOF
                    scale = 10
                    i = $interest / 12
                    p = $principal
                    n = $months
                    a = p * ((i * ((1 + i) ^ n)) / (((1 + i) ^ n) - 1))
                    print a, "\n"
                EOF
                实验例子:                
                    [seven@localhost playground]$ 35.4.2.txt 135000 0.0775 180
                    bash: 35.4.2.txt: command not found
                    [seven@localhost playground]$ bash 35.4.2.txt 135000 0.0775 180
                    1270.7222490000

第三十六章36-数组
    数组的特性是:允许存放多个值在一个变量中。shell支持数组,
    学习数组,让我们的shell命令更强大。

36.1    什么是数组
            数组是一次能存放多个数据的变量,数组里面有单元格,一个单元格存一个元素,
            bash中的数组仅限制为单一维度,尽管有限制,它依然很流行。
            shell版本2开始支持数组,这意味着,原来的unix shell程序,sh根本就不支持数组。

36.2    创建一个数组
            和其他脚本语言一样,变量会在第一次访问时自动创建,也可以事先用declare声明
            
            [seven@localhost playground]$ abc[1]=foot
            [seven@localhost playground]$ echo ${abc[1]}
            foot
            [seven@localhost playground]$ declare -a abc
            [seven@localhost playground]$ echo ${abc[1]}
            foot

36.3    数组赋值
            给一个数组各个元素,批量赋值使用小括号,不可使用大括号
            foo[223]=value
            foo=(3 4 5 6)
            days=(Sun Mon Tue Wed Thu Fri Sat)
            days=([0]=Sun [1]=Mon [2]=Tue)
            
36.4    访问数组元素
            hours命令,被用来确定什么时段一个系统最活跃,对当前目录中的文件修改次数进行统计。

            hours脚本如下:
            #!/bin/bash
            # hours : script to count files by modification time
            usage (){
                echo "usage: $(basename $0) directory" >&2
            }
            # Check that argument is a directory
            if [[ ! -d $1 ]]; then
                usage
                exit 1
            fi
            # Initialize array
            for i in {0-23}; do hours[i]=0; done
            # Collect data
            for i in $(stat -c %y "$1"/* | cut -c 12-13); do
                j=$(i/#0)
                ((++hours[j]))
                ((++count))
            done
            # Display data
            echo -e "Hour\tFiles\tHour\tFiles"
            echo -e ----\t----\t----\t----""    
            for i in {0..11}; do
                j=$((i + 12))
                printf "%02d\t%d\t%02d\t%d\n" $i ${hours[i]} $j ${hours[j]}
            done
            printf "\nTotal files = %d\n" $count

            实验例子:
            [seven@localhost playground]$ bash 36.4.txt .
                。。。。。。。。。。。。。。。。。
              Hour    Files    Hour    Files
                ----t----t----t----
                00    32    12    0
                01    13    00    0
                02    14    00    0
                03    15    00    0
                04    16    00    0
                05    17    00    0
                06    18    00    0
                07    19    00    0
                08    20    00    0
                09    21    00    0
                10    22    00    0
                11    23    00    0

                Total files = 32


             

36.5    数组操作
            学习删除数组,确定数组大小,排序等操作。
            *和@可以被用来访问数组中的每个元素。
            表示法${animals[*]}和{animals[@]}的行为是一致的除非被引号引起来
            animals=("a dog" "a cat" "a fish")

            实验例子:
                [seven@localhost playground]$ for i in ${animals[@]}; do echo $i; done
                a
                dog
                a
                cat
                a
                fish


               [seven@localhost playground]$ for i in "${animals[*]}"; do echo $i; done
                a dog a cat a fish
                [seven@localhost playground]$ for i in "${animals[@]}"; do echo $i; done
                a dog
                a cat
                a fish
                
            统计数组元素个数,特别注意shell中的数组空白元素不统计
            统计数组个数的用法  ${#array[@]}
            a[100]=foot
            echo ${#a[@]}
            echo ${#a[100]}
            实验例子:
                [seven@localhost playground]$ a[100]=foot
                [seven@localhost playground]$ echo ${#a[@]}
                1
                [seven@localhost playground]$ echo ${#a[100]}
                4


            数组使用的下标:前面说,shell中不统计空白元素的个数,我们该如何找到?
                正因为shell允许赋值的数组元素间存在间隔,有时候确定那个元素存在得花点脑经。
            关键点:*或者@ 能展开成分离的词,加上感叹号不输出内容只输出下标。
            ${!array[*]}
            ${!array[@]}

            for=([2]=a [4]=b [6]=c)
            for i in "${foo[@]}"; do echo $i; done
            for i in "${!foo[@]}"; do echo $i; done
            实验例子:
                [seven@localhost playground]$ for i in "${foo[@]}"; do echo $i; done
                a
                b
                c
                [seven@localhost playground]$ for i in "${!foo[@]}"; do echo $i; done
                2
                4
                6


            数组末尾添加元素
            想在末尾添加元素,知道数组中的个数是没用的,因为shell中的数组空白元素不统计,
            通过*和@表示法,返回的数值也不能告诉我们使用的最大数组索引。我们需要学习新的用法。
            foo=(a b c)
            echo ${foo[@]}
            foo+=(d e f)
            echo ${foo[@]}
            实验例子:
                [seven@localhost playground]$ a[100]=foot;
                [seven@localhost playground]$ a+=(4 5 6)
                [seven@localhost playground]$ echo ${!a[@]}
                100 101 102 103
                [seven@localhost playground]$ echo ${a[@]}
                foot 4 5 6

            数组排序,shell中没有数组排序的方法,需要我们自己去实现

            脚本如下:
                #!/bin/bash
                # array-sort : Sort an array
                a=(f e d c b a)
                echo "Original array: ${a[@]}"
                a_sorted=($(for i in "${a[@]}"; do echo $i; done | sort)) # 为什么要在for前使用$,这里的$是给sort使用的。
                echo "Sorted array: ${a_sorted[@]}"

            实验例子:
                [seven@localhost playground]$ bash array-sort
                Original array: f e d c b a
                Sorted array: a b c d e f

            删除数组:

            foo=(a b c d e f)
            echo ${foo[@]}

            unset foo
            echo ${foo[@]}

            foo=(a b c d e f)
            echo ${foo[@]}

            unset 'foo[2]'
            echo ${foo[@]}

            foo=(a b c d e f)
            foo=
            echo ${foo[@]}
            这个例子说明,任何引用一个不带下标的数组变量,则指的是数组元素0
            实验例子:
                [seven@localhost playground]$ echo ${foo[@]}
                a b c d e f
                [seven@localhost playground]$ unset foo
                [seven@localhost playground]$ echo ${foo[@]}

                [seven@localhost playground]$ foo=(a b c d e f)
                [seven@localhost playground]$ foo=
                [seven@localhost playground]$ echo ${foo[@]}
                 b c d e f

            
36.6    关联数组
            最新版本的特性,
            关联数组使用字符串作为数组索引。
            declare -A colors
            colors["red"]="#ff0000"
            colors["green"]="#00ff00"
            colors["blue"]="#0000ff"

            echo ${colors["blue"]}

            书中说,关联数组必须使用带 -A 选项的declare命令创建,
            在center os7上实验发现两种方法都可以实现。
            实验例子:        
                [seven@localhost playground]$ color["abc"]="blue"
                [seven@localhost playground]$ echo ${color["abc"]}
                blue

第三十七章37-奇珍异宝
    本章是本书最后一站,这将介绍些零星的知识点。
    之前的400多页里,linux shell的很多方面都已佘略,
    但是还有许多bash特性没有佘略,在本书的最后,
    说点虽然不常用的知识点,但是对某些程序可能很有帮助。

37.1    组命令和子shell
            把命令组合在一起执行,
            组命令使用花括号,子shell用括号。
            注意语法要求,花括号与命令要有空格,并且最后一个命令必须用一个分号,或者一个换行符。

            { ls -l; echo "Listing of foo.txt"; cat foo.txt; } > output.txt
            (ls -l; echo "Listing of foo.txt"; cat foo.txt) > output.txt
            { ls -l; echo "Listing of foo.txt"; cat foo.txt; } | lpr

            进程替换的问题:
            组命令和子shell看上去很相似,都可以重定向中合并流,但是两者之间有一个很重要的不同。
            组命令在当前shell中执行它的所有命令,而一个子shell在当前shell的一个子副本中执行它的命令。
            所以,大多数情况下,倾向于使用组命令。

            echo "foo" | read
            echo $REPLY

            因为read命令在子shell中执行,REPLY副本会自动销毁。
            如果非得使用子shell,可以使用 <(list) 叫做进程替换技术

            <(list) 适用于产生标准输出的进程
            >(list) 适用于产生标准输入的进程

            这里的list是一串命令的集合列表,

            read < <(echo "foo")
            echo $REPLY

            进程替换允许我们把一个子shell的结果单做一个用于重定向的普通文件。

            echo <(echo "foo")

            进程替换经常被包含read命令的循环用到。
            脚本如下:
                #!/bin/bash
                # pro-sub : demo of process substitution
                while read attr links owner group size date time filename; do
                    cat <<- EOF
                        Filename: $filename
                        Size: $size
                        Owner: $owner
                        Group: $group
                        Modified: $date $time
                        Links: $links
                        Attributes: $attr
                    EOF
                done < <(ls -l | tail -n +2)
            实验例子:(此脚本报语法错误)
            
37.2    陷阱
            若一个脚本接收到即将提前终止的信号,此时让脚本删除创建的临时文件,这对一个大项目很有用。
            为了满足这个需求,bash提供了trap机制,就是众所周知的陷阱。
            语法为: trap argument signal[signal...]
            argument 是一串字符串,它被读取并单做一个命令,
            signal 是一个信号的说明,它会触发执行所要解释的命令。

            SIGINT 或 SIGTERM 信号
            
            这个脚本运行需要25秒,如果提前用ctrl+c 结束就会触发 echo
            脚本如下:
                #!/bin/bash
                # trap-demo : simple signal handling demo
                trap "echo 'I am ignoring you.'" SIGINT SIGTERM
                for i in {1..5}; do
                    echo "Iteration $i of 5"
                    sleep 5
                done

            构建一个字符串形成一个有用的命令序列是很笨拙的,通常会指定一个shell
            函数作为命令,每个型号指定一个单独的shell函数来处理。

            脚本如下:                                
                #!/bin/bash
                # trap-demo2 : simple signal handling demo
                exit_on_signal_SIGINT () {
                    echo "Script interrupted." 2>&1
                    exit 0
                }
                exit_on_signal_SIGTERM () {
                    echo "Script terminated." 2>&1
                    exit 0
                }
                trap exit_on_signal_SIGINT SIGINT
                trap exit_on_signal_SIGTERM SIGTERM
                for i in {1..5}; do
                    echo "Iteration $i of 5"
                    sleep 5
                done
            
37.3    异步执行
            异步是各干各的,想要保持父子脚本之间协调工作,又想异步,这得使用wait,
            同步执行的缺点是,一个脚本必须等待另一个脚本结束任务之后,才能完成自己的任务。
            在shell中,使用wait命令会导致父脚本暂停运行,直到一个特定的进程结束运行。
            wait命令演示,需要两个脚本,一个父脚本,一个子脚本。
            
            父脚本如下:
            #!/bin/bash
            # async-parent : Asynchronous execution demo (parent)
            echo "Parent: starting..."
            echo "Parent: launching child script..."
            async-child &
            pid=$!
            echo "Parent: child (PID= $pid) launched."
            echo "Parent: continuing..."
            sleep 2
            echo "Parent: pausing to wait for child to finish..."
            wait $pid
            echo "Parent: child is finished. Continuing..."
            echo "Parent: parent is done. Exiting."
            
            子脚本如下:
            #!/bin/bash
            # async-child : Asynchronous execution demo (child)
            echo "Child: child is running..."
            sleep 5
            echo "Child: child is done. Exiting."
            
37.4    命名管道
            命名管道的行为类似于文件,但实际上形成了FIFO的缓冲,和普通(未命名)管道一样
                数据从一端进入,然后从另一端出现。
                process1 | named_pipe
                和
                process2 | named_pipe
                表现出来的形式是  process1 | process2

            实验例子:
                第一步,设置一个命名管道: mkfifo pipe1
                    ls -l pipe1
                第二步,我们需要两个shell窗口来演示
                    在第一个终端中,ls -l > pipe1,此时会阻塞。
                    在第二个终端中,cat < pipe1,执行完成后,两个命令都已结束。
                    注意: 两个终端得在一个目录下执行命令例如同在 playground文件夹下。 

37.5    总结
            学完本书,shell命令剩下的唯一要做的事就是练习,再练习。纵然我们一路过来敲了很多,
            但只是接触了它的表面,做到熟能生巧,在实际的开发生产中,才会得心应手。                    
                                     

 类似资料: