Shell入门看我吧

背景

之前写了系列的shell实战的文章,得到了挺多小伙伴的关注,遇到有些对shell不太熟悉小伙伴,所以有个想法写一篇shell入门的文章。
时间流逝,再回头去看看过去的东西,才会发现哪些东西比较重要,故撰此文,记录我在过去学习和使用shell语言过程中我个人觉得比较重要的部分,做一个小总结和分享。

文章中使用到的代码位置:
https://gitee.com/dhar/YTTInjectedContentKit/tree/master/DevPods/InjectedContentKit/Example/injectContentShell

https://gitee.com/dhar/ShellLearning/tree/master/helloshell

文章内容结构如下:

  • 语法

    • 变量
    • 打印
    • 运算
    • 控制
    • 循环
    • 容器
    • 方法
  • 文件

    • 文件读取
    • 文件写入
  • sed流编辑

  • 模块

    • 工具模块
    • 流程模块
  • 输入和菜单

    • 获取输入
    • 菜单

语法

变量

变量的定义

定义一个变量和其他语言的类似,shell是弱类型语言所以不需要使用类型限定,并且变量可以修改类型,下面的例子定义了一个字符串类型的str变量,之后修改为数值类型

注意点:变量等号两边不能有空格出现

str="string"
echo $str
echo ${str}
str=123
echo $str

变量使用

变量可以赋值给另一个变量和打印,当变量被使用的时候需要在变量名称前面加上$符号,还有另一种方式是把变量名放在${}括号中使用,可以把命令执行结果作为右值赋值给变量

str2=$str
echo $str2;
str3=${str}
echo ${str3}
curDir=$(pwd)
echo "curDir = ${curDir}"
curDirCon=`ls`
echo "curDirCon = ${curDir2}"

# 输出:
=======变量=======
string
string
123
123
123
curDir = /Users/aron/git-repo/ShellLearning/helloshell
curDirCon = data
syntax.sh

打印

因为shell没有单步调试和其他功能强大的IDE,所以打印功能就经常使用到,此外打印功能还可以当做函数的返回值,比return作为函数的返回值功能更强大,shell 使用echo打印,内容默认输出到控制台中,echo可以打印字符串、变量、以及字符串中嵌入变量的混个内容,echo有几重要的参数选项

  • -e 转义字符处理,比如\t显示为制表符而不是显示输出\t
  • -n 把文本字符串和命令输出显示在同一行中
str4="string4"
echo $str4
echo "str4=$str4"
echo "str4=${str4}str3=${str3}"

# 输出:
=======打印=======
string4
str4=string4
str4=string4str3=123

运算

使用expr执行算术运算

注意点:*乘法运算符号需要转义

echo "=======运算======="
result=$(expr 5 + 5)
echo ${result}
result=$(expr 16 - 5)
echo ${result}
result=$(expr 5 \* 5)
echo ${result}
result=$(expr 28 / 5)
echo ${result}

# 输出:
=======expr运算=======
10
11
25
5

使用[]执行算术

[]执行算术比expr简单多了,并且*乘法运算符号不需要转义

echo "=======[]运算======="
result=$[5 + 5]
echo ${result}
result=$[16 - 5]
echo ${result}
result=$[5 * 5]
echo ${result}
result=$[28 / 5]
echo ${result}

# 输出:
=======[]运算=======
10
11
25
5

控制

数值比较

控制使用if/else/fi语法,典型的数值比较如下

if [[ 3 > 7 ]]; then
    echo "hehe"
else
    echo "yes"
fi

# 输出:
yes

还可以使用下面的比较符号

比较符 描述
n1 -eq n2 检查n1是否与n2相等
n1 -ge n2 检查n1是否大于或等于n2
n1 -gt n2 检查n1是否大于n2
n1 -le n2 检查n1是否小于或等于n2
n1 -lt n2 检查n1是否小于n2
n1 -ne n2 检查n1是否不等于n2

一个简单的9*9乘法口诀表的例子

echo "9*9======="
i=1
j=1
line=""
while [[ i -lt 10 ]]; do
    j=1
    line=""
    until [[ j -eq 10 ]]; do
        if [[ j -le i ]]; then
            result=$(expr $i \* $j)
            resultStr="$j X $i = $result"
            line=${line}${resultStr}"\t"
        fi
        j=$(expr $j + 1)
    done
    echo -e ${line}
    i=$(expr $i + 1)
done

# 输出:
9*9=======
1 X 1 = 1   
1 X 2 = 2   2 X 2 = 4   
1 X 3 = 3   2 X 3 = 6   3 X 3 = 9   
1 X 4 = 4   2 X 4 = 8   3 X 4 = 12  4 X 4 = 16  
1 X 5 = 5   2 X 5 = 10  3 X 5 = 15  4 X 5 = 20  5 X 5 = 25  
1 X 6 = 6   2 X 6 = 12  3 X 6 = 18  4 X 6 = 24  5 X 6 = 30  6 X 6 = 36  
1 X 7 = 7   2 X 7 = 14  3 X 7 = 21  4 X 7 = 28  5 X 7 = 35  6 X 7 = 42  7 X 7 = 49  
1 X 8 = 8   2 X 8 = 16  3 X 8 = 24  4 X 8 = 32  5 X 8 = 40  6 X 8 = 48  7 X 8 = 56  8 X 8 = 64  
1 X 9 = 9   2 X 9 = 18  3 X 9 = 27  4 X 9 = 36  5 X 9 = 45  6 X 9 = 54  7 X 9 = 63  8 X 9 = 72  9 X 9 = 81  
=======  =======

字符串比较

比较符 描述
str1 = str2 检查str1是否和str2相同
str1 != str2 检查str1是否和str2不同
str1 < str2 检查str1是否比str2小
str1 > str2 检查str1是否比str2大
-n str1 检查str1的长度是否非0
-z str1 检查str1的长度是否为0
echo "=======控制字符串比较======="
str1="abc"
str2="abd"
if [[ $str1 > $str2 ]]; then
    echo "$str1 大于 $str2"
else
    echo "$str1 小于等于 $str2"
fi

if [[ -z $str1 ]]; then
    echo "str1 为空"
else
    echo "str1 不为空"
fi

str1=""
if [[ -z $str1 ]]; then
    echo "str1 为空"
else
    echo "str1 不为空"
fi


# 输出:
=======控制字符串比较=======
abc 小于等于 abd
str1 不为空
str1 为空

文件比较

比较符 描述
-d file 检查file是否存在并是一个目录
-e file 检查file是否存在
-f file 检查file是否存在并是一个文件
-r file 检查file是否存在并可读
-s file 检查file是否存在并非空
-w file 检查file是否存在并可写
-x file 检查file是否存在并可执行
-O file 检查file是否存在并属当前用户所有
-G file 检查file是否存在并且默认组与当前用户相同
file1 -nt file2 检查file1是否比file2新
file1 -ot file2 检查file1是否比file2旧
echo "=======控制文件比较======="
file="syntax.sh"
if [[ -e $file ]]; then
    echo "${file} 文件存在"
else
    echo "${file} 文件不存在"
fi

if [[ -f $file ]]; then
    echo "${file} 是一个文件"
else
    echo "${file} 不是一个文件"
fi

if [[ -d $file ]]; then
    echo "${file} 是一个文件夹"
else
    echo "${file} 不是一个文件夹"
fi

# 输出:
=======控制文件比较=======
syntax.sh 文件存在
syntax.sh 是一个文件
syntax.sh 不是一个文件夹

循环

C语言格式的for循环

echo "=======循环for======="

num=0
for (( i = 0; i < 10; i++ )); do
    num=$[$num + $i]
done
echo "result = ${num}"

# 输出:
=======循环for=======
result = 45

for in 循环处理文件

data文件内容如下:

➜  helloshell git:(master) ✗ cat data
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.%     
echo "=======循环for in======="

file="data"
IFS_OLD=$IFS
IFS=$'\n'
for line in $(cat $file)
do
    echo "${line}"
done
IFS=${IFS_OLD}

# 输出:
=======循环for in=======
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.

while 循环

while表示条件满足执行循环,比如下面的例子是9*9乘法口诀表中的一部分,表示i从1循环到9

i=1
while [[ i -lt 10 ]]; do
    i=$(expr $i + 1)
done

until 循环

untilwhile相反,表示条件不满足执行循环,比如下面的例子是9*9乘法口诀表中的一部分,表示j从1循环到9

j=1
    line=""
    until [[ j -eq 10 ]]; do
        if [[ j -le i ]]; then
            result=$(expr $i \* $j)
            resultStr="$j X $i = $result"
            line=${line}${resultStr}"\t"
        fi
        j=$(expr $j + 1)
    done
    echo -e ${line}

容器

数组的定义如下 declare -a array_name

注意:osx系统因为bash的版本太低,只能定义索引数组,在bash版本高于4.1的版本可以使用declare -A array_name定义关联数组

以下的代码片定义一个数组,用于保存配置文件中的内容,然后使用for循环遍历数组内容输出到控制台。

  • config_content_array[$cfg_line_count]=$line 把内容保存到数组
  • ${#config_content_array[@]} 获取数组的元素个数
  • config_content=${config_content_array[i]}; 读取数组第i个元素
####### 数据定义
# 定义保存类名称的数组
declare -a config_content_array
cfg_line_count=0

# 1、读取配置文件内容保存到数组中
read_config_content_to_array() {
    # 读取配置文件
    echo "开始读取配置文件..."
    # mark: p291
    IFS_OLD=$IFS
    IFS=$'\n'
    # 删除文件行首的空白字符 http://www.jb51.net/article/57972.htm
    for line in $(cat $cfg_file | sed 's/^[ \t]*//g')
    do
        is_comment=$(expr "$line" : '^#.*')
        echo "line=${line} is_common=${is_comment}"
        if [[ ${#line} -eq 0 ]] || [[ $(expr "$line" : '^#.*') -gt 0 ]]; then
            echo "blank line or comment line"
        else
            config_content_array[$cfg_line_count]=$line
            cfg_line_count=$[ $cfg_line_count + 1 ]
            echo "line>>>>${line}"
        fi  
    done
    IFS=${IFS_OLD}

    for (( i = 0; i < ${#config_content_array[@]}; i++ )); do
        config_content=${config_content_array[i]};
        echo "config_content>>>>>>${config_content}"
    done
}

方法

方法的定义有两种方式

  • function func1 { #这里定义方法体 }
  • func2() { #这里定义方法体 }

方法返回值的处理有三种方式

  • return 最大返回256,表示结果码
  • echo 返回
  • 保存在全局变量中

方法的参数

  • 参数的传递添加在方法之后,多个使用空格分割
  • 参数的获取使用$1,$2以此类推,特别地$0表示文件名、$#表示参数的个数
echo "=======方法======="

function func1 {
    echo "func1 invoked"
    # 最大的返回值为256,超过了256取模的结果,280%256=24,最终返回24
    return 280;
}

func2() {
    echo "return value"
}

# 检测文件夹存在的方法,结果保存在全局变量`CheckInputDestDirRecursiveReturnValue`中
# 参数一:检测的文件夹路径
# 参数二:提示消息字符串
# 使用方式如下,去掉注释
# # 导入工具脚本
# . ./FileUtil.sh
# # 检测class_search_dir
# checkDirCore $class_search_dir "指定类的查找目录不存在"
# class_search_dir=${CheckInputDestDirRecursiveReturnValue}
checkDirCore() {
    to_process_dir=$1
    message=$2
    echo "scriptName=${0} paramsCount=${#}"
    # 需处理源码目录检查
    if [[ -d $to_process_dir ]]; then
        echo "目录存在 $to_process_dir"
        CheckInputDestDirRecursiveReturnValue=$to_process_dir
        return 1
    else
        echo "${message} ${to_process_dir}"
        checkInputDestDirRecursive ${to_process_dir}
    fi
}

echo `func1`
echo `func2`

func1
retValue=$?
echo "func1 retValue=$retValue"

retValue=`func2`
echo "func2 retValue=$retValue"

checkDirCore $(pwd) "指定类的查找目录不存在"
dir=${CheckInputDestDirRecursiveReturnValue}
echo "dir = ${dir}"

# 输出:
=======方法=======
func1 invoked
return value
func1 invoked
func1 retValue=24
func2 retValue=return value
scriptName=./syntax.sh paramsCount=2
目录存在 /Users/aron/git-repo/ShellLearning/helloshell
dir = /Users/aron/git-repo/ShellLearning/helloshell

文件

文件读取

文件的读取可以使用cat命令结合for in循环处理

注意:$IFS是文件循环处理的分隔符,按按行处理数据需要把该值设置为$'\n',处理完成之后恢复旧值

echo "=======文件======="
file="data"
IFS_OLD=$IFS
IFS=$'\n'
for line in $(cat $file)
do
    echo "${line}"
done
IFS=${IFS_OLD}

输出:
=======文件=======
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.

目录的读取

文件的读取可以使用ls命令结合for in循环处理

echo "=======文件目录======="
function read_implement_file_recursively {
    if [[ -d $1 ]]; then
        for item in $(ls $1); do
            itemPath="$1/${item}"
            if [[ -d $itemPath ]]; then
                # 目录
                echo "处理目录 ${itemPath}"
                read_implement_file_recursively $itemPath
            else 
                # 文件
                echo "处理文件 ${itemPath}"
            fi
        done
    else
        echo "err:不是一个目录"
    fi
}

read_implement_file_recursively $(pwd)

输出:
=======文件目录=======
处理文件 /Users/aron/git-repo/ShellLearning/helloshell/data
处理目录 /Users/aron/git-repo/ShellLearning/helloshell/subfolder
处理文件 /Users/aron/git-repo/ShellLearning/helloshell/subfolder/data2
处理文件 /Users/aron/git-repo/ShellLearning/helloshell/syntax.sh

文件写入

使用输出重定向把内容输出到文件

  • >输出重定向符号先清空文件然后把内容写入到文件中
  • >>输出重定向符号把内容追加写入到文件中

此外可以结合其他命令实现排序、去重功能

  • sort命令对文件内容以行作为单位排序
  • uniq命令对文件内容进行去重,以行为单位,一般需要结合sort命令使用
file="subfolder/data2"
destfile="subfolder/data2-p"
sort ${file} | uniq > ${destfile}

结果:
➜  helloshell git:(master) ✗ cat subfolder/data2
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown
fox
jumps over the lazy dog.%                                                                                          
➜  helloshell git:(master) ✗ cat subfolder/data2-p 
The quick brown
The quick brown fox jumps over the lazy dog.
fox
jumps over the lazy dog.

sed流编辑

Sed流编辑结合正则表达式可以方便的对文本文件进行查询、修改、删除、增加等操作

注意:osx系统自带的sed命令和标准的gun-sed使用方式上有些差别,所以以下篇幅所谈论到的sed都是标准的gun-sed,下面的这个脚本用户判断系统是否安装了gun-sed,如果没有回自动进行安装,完成之后需要用户执行显示的显示的命令配置下即可。

# 检测是否安装gun sed,mac 内置的sed会有些问题,所以需要安装gun sed
gunSedInstallCheck() {
    # 检查是否安装gunsed
    # mac安装gunSed  http://blog.csdn.net/sun_wangdong/article/details/71078083
    which_sed=`which sed`
    echo $which_sed
    echo "testresult = $(expr "$which_sed" : '.*/gnu-sed/')"
    which_sed=`ls -al ${which_sed}`
    echo $which_sed
    echo "testresult = $(expr "$which_sed" : '.*/gnu-sed/')"
    if [[ $(expr "$which_sed" : '.*/gnu-sed/') -gt 0 ]]; then
        echo "检测到使用gun sed"
    else
        if [ ! `which brew` ]
        then
            echo 'Homebrew not found. Trying to install...'
                        ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" \
                || exit 1
        fi
        echo 'Trying to install gun sed...'
        brew install gnu-sed --with-default-names || exit 1
        # 设置局部环境变量
        echo "set PATH...."
        source ./set-gun-sed-path.sh
        echo "set PATH done"

        echo "请手动执行命令,然后重新执行"
        command="PATH=\"/usr/local/Cellar/gnu-sed/4.4/bin:\$PATH\""
        echo $command
        echo ""
        exit 1
    fi
}

sed命令功能繁多,所以这里只讲讲我在实战中使用到的,首先了解小sed命令的结构

sed -param operation/pattern/replacement/flags

sed param

  • -e script 在处理输入时,将script中指定的命令添加到已有的命令中
  • -f file 在处理输入时,将file中指定的命令添加到已有的命令中
  • -n 不产生命令输出,使用print命令来完成输出
  • -i 把修改写入到原文件中

sed operation

  • s 替换
  • a 追加
  • d 删除

sed pattern/replacement

查找对应的模式和匹配模式的替换内容

sed flag

有4种可用的替换标记:

  • 数字,表明新文本将替换第几处模式匹配的地方;
  • g,表明新文本将会替换所有匹配的文本;
  • p,表明原先行的内容要打印出来;
  • w file,将替换的结果写到文件中。

sed添加内容

以下是injectContentShell#injectedContentShell.sh脚本文件中的代码片段,使用a操作吧内容添加到方法的开头

  • /^- \(.*\){$/这部分是pattern,匹配OC中方法的开始
  • a\ '"$injected_content"'这部分是operation,注意其中插入内容的变量要使用双引号和单引号包含处理
# 在匹配的行下面添加插入内容
sed -i '/^- \(.*\){$/{
    a\ '"$injected_content"'
}' ${file}

sed删除内容

以下是injectContentShell#injectedContentShell.sh脚本文件中的代码片段,使用d操作删除内容

sed -i '/'"$pattern_str"'/ {
    d
}' ${file}

sed修改替换内容

以下是injectContentShell#RenameClasses.sh脚本文件中的代码片段,使用s操作替换内容,有以下几个要点

  • s/'"${original_class_name}"'/'"${result_class_name}"'/g,使用s操作,注意patternreplacement中变量的处理方式,使用双引号、单引号双重包含,使用flagg表示全部替换
  • grep ${original_class_name} -rl ${pbxproj_dir}grep命令查找${pbxproj_dir}文件夹下所有出现${original_class_name}内容的文件,-r选项表示递归查找,-l选项表示只显示匹配到的文件,返回的结果可能是多个的。
sed -i '{
    s/'"${original_class_name}"'/'"${result_class_name}"'/g
}' `grep ${original_class_name} -rl ${pbxproj_dir}`
sed -i '{
    s/'"${original_class_name}"'/'"${result_class_name}"'/g
}' `grep ${original_class_name} -rl ${class_name_replace_dir}`

模块

shell是面向过程的语言,不具备面向对象的特性,shell可以把部分功能独立分离出来,放在单独的脚本文件中,其他模块可以导入该脚本文件,使用其中的功能,这就是shell的伪面向对象

工具模块

工具模块是包含了工具方法的模块,比如数学计算可以放在一个单独的文件中独立为一个模块,其他需要使用到的地方引入这个模块,使用其中定义的方法即可

Math.sh保存了一些数学计算函数

#!/bin/bash

power(){
    base=$1
    exp=$2
    result=1
    for (( i = 0; i < $exp; i++ )); do
        result=$[ $result * $base ];
    done
    echo $result
}

其他模块使用. ./Math.sh包含这个模块,可以调用其中定义的power方法

注意:. ./Math.sh.source的简写,这里也可以写成source ./Math.sh

echo "=======模块======="

. ./Math.sh

result=$(power 3 5)
echo "3^5 = ${result}"

输出:
=======模块=======
3^5 = 243

流程模块

流程模块是包含了一些列操作的模块,可以向该模块传递参数,也可以有返回值。流程模块中有两个地方比较特别,一个是流程模块本身参数的处理方式和外部调用流程模块传入参数的方式

流程模块处理参数

流程模块处理参数使用getopts命令实现,getopts optionstring opt其中optionstring格式:i:o:io之后的:表示指定i选项和o选项需要有参数,第一个:表示忽略错误,使用case分支处理参数选项对应的参数值。

#### 参数解析
echo "参数>>${@}"
while getopts :i:o: opt
do
    case "$opt" in
        i) param_input_dir=$OPTARG
            echo "Found the -i option, with parameter value $OPTARG"
            ;;
        o) param_output_file=$OPTARG
            echo "Found the -o option, with parameter value $OPTARG"
            ;;
        *) echo "Unknown option: $opt";;
    esac
done
echo "param_input_dir = ${param_input_dir}"
echo "param_output_file = ${param_output_file}"

参数的传递

参数的传递和使用命令行的选项类似,可以在选项后面添加该选项的参数值

./GetAndStoreClasses.sh\
    -i ${class_search_dir}\
    -o ${cfg_file}

下面定义的是一个流程模块的脚本文件,是injectContentShell#GetAndStoreClasses.sh脚本文件中的代码片段,实现了生成重命名的类的配置脚本功能,可以传递两个参数。

#!/bin/bash
########################
# 脚本功能:生成重命名的类的配置脚本
# 输入参数 -i 输入的文件夹
# 输入参数 -o 保存的文件
########################


####### 参数定义
param_input_dir=""
param_output_file=""


####### 参数解析
echo "参数>>${@}"
while getopts :i:o: opt
do
    case "$opt" in
        i) param_input_dir=$OPTARG
            echo "Found the -i option, with parameter value $OPTARG"
            ;;
        o) param_output_file=$OPTARG
            echo "Found the -o option, with parameter value $OPTARG"
            ;;
        *) echo "Unknown option: $opt";;
    esac
done
echo "param_input_dir = ${param_input_dir}"
echo "param_output_file = ${param_output_file}"


####### 配置

# 属性黑名单配置文件
blacklist_cfg_file="$(pwd)/DefaultClassesBlackListConfig.cfg"


####### 数据定义

# 定义保存需要处理目标文件的数组
declare -a implement_source_file_array
declare -a implement_source_file_name_array
implement_source_file_count=0

# mark: p384
# 递归函数读取目录下的所有.m文件
function read_implement_file_recursively {
    echo "read_implement_file_recursively"
    if [[ -d $1 ]]; then
        for item in $(ls $1); do
            itemPath="$1/${item}"
            if [[ -d $itemPath ]]; then
                # 目录
                echo "处理目录 ${itemPath}"
                read_implement_file_recursively $itemPath
                echo "处理目录结束====="
            else 
                # 文件
                echo "处理文件 ${itemPath}"
                if [[ $(expr "$item" : '.*\.m') -gt 0 ]]; then
                    echo ">>>>>>>>>>>>mmmmmmm"
                    implement_source_file_array[$implement_source_file_count]=${itemPath}
                    class_name=${item//".m"/""};
                    implement_source_file_name_array[$implement_source_file_count]=${class_name}
                    implement_source_file_count=$[ implement_source_file_count + 1 ];
                fi
                echo ""
            fi
        done
    else
        echo "err:不是一个目录"
    fi
}

post_implement_file_handle() {
    local wirte_to_file=$1
    # 写入文件中
    echo "# 需要处理的类配置文件" > ${wirte_to_file}
    for(( i=0;i<${#implement_source_file_name_array[@]};i++)) 
    do 
        class_file_name=${implement_source_file_name_array[i]}; 
        echo ${class_file_name} >> ${wirte_to_file}
    done;

    # 去重
    wirte_to_file_bak="${wirte_to_file}.bak"
    mv ${wirte_to_file} ${wirte_to_file_bak}
    sort ${wirte_to_file_bak} | uniq > ${wirte_to_file}

    # 过滤
    mv ${wirte_to_file} ${wirte_to_file_bak}
    echo "# Properties Configs Filtered" > ${wirte_to_file}
    IFS_OLD=$IFS
    IFS=$'\n'
    # 上一行的内容
    lastLine="";
    for line in $(cat ${wirte_to_file_bak} | sed 's/^[ \t]*//g')
    do
        grep_result=$(grep ${line} ${blacklist_cfg_file})
        category_judge_substring="\+"
        if [[ ${#line} -le 6 ]] || [[ $(expr "$line" : '^#.*') -gt 0 ]] || [[ -n ${grep_result} ]] || [[ ${line} =~ ${category_judge_substring} ]]; then
            # 长度小于等于6、注释内容的行、在黑名单中的内容、分类文件不处理
            echo "less then 6 char line or comment line"
        else
            if [[ -n ${lastLine} ]]; then
                # 上一行是非空白行
                # 比较上一行内容是否是当前行的一部分,不是添加上一行
                if [[ ${line} =~ ${lastLine} ]]; then
                    echo "${line} 和 ${lastLine} 有交集"
                else
                    echo ${lastLine} >> ${wirte_to_file}
                fi
            fi
            # 更新上一行
            lastLine=${line}
        fi  
    done
    IFS=${IFS_OLD}

    # 删除临时文件
    rm -f ${wirte_to_file_bak}
}

read_implement_file_recursively ${param_input_dir}
post_implement_file_handle ${param_output_file}

在另一个模块中使用流程模块

# 获取需要重命名的类名称,保存到配置文件中
./GetAndStoreClasses.sh\
    -i ${class_search_dir}\
    -o ${cfg_file}

输入和菜单

获取输入

下面是一个循环的输入和检测输入是否是合法目录的例子,是injectContentShell#FileUtil.sh脚本文件中的代码片段

  • echo -n "请输入目录: "是输入的提示,-n表示不换行,用户的输入跟随在提示后面
  • read path把用户的输入内容保存在变量path中
# 循环检测输入的文件夹
checkInputDestDirRecursive() {
    echo -n "请输入目录: "
    read path
    if [[ -d $path ]]; then
        CheckInputDestDirRecursiveReturnValue=$path
    else
        echo -n "输入的目录无效,"
        checkInputDestDirRecursive
    fi
}

菜单

在脚本中可能会有使用菜单选项进行交互的场景,有以下几个要点

  • read -n 1 option命令中用了-n选项来限制只读取一个字符。这样用户只需要输入一个数字,也不用按回车键,输入的内容保存在option变量中
  • clear命令是用来清空命令行的屏幕的
  • echo -e -e 选项用来处理转义字符
  • echo -en -n 选项让光标处于同一行,用户的输入会显示在同一行
  • 使用while循环获取用户的输入,在while循环中使用case分支处理不同的操作

以下脚本是injectContentShell#injectedContentShell.sh文件中的一部分

function genMunu {
    clear
    echo
    echo -e "\t\t\t选项菜单\n"
    echo -e "\t1. 删除注入内容"
    echo -e "\t2. 添加注入内容"
    echo -e "\t0. Exit menu\n\n"
    echo -en "\t\tEnter option: "
    read -n 1 option
}


while [[ 1 ]]; do
    genMunu
    case $option in
    0 )
        echo ""
        echo "Bye"
        exit 0
    ;;
    1 )
        # 删除配置文件中注入的内容
        removeInjectedContent
    ;;
    2 )
        # 添加配置文件中注入的内容
        addInjectedContent
    ;;
    h )
        genMunu
    ;;
    * )
        echo "Wrong!!"
    ;;
    esac

    echo
    echo -en "\n\n\tHit any key to continue"
    read -n 1 line

done
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350