bash(上)

基础

  • !!可以执行上一条命令

  • Ctrl+/可以强行终止进程(在Ctrl+C不管用时可以试试)

  • 一些快捷键

    | 快捷键 | 含义 |
    | :———: | :——————————————— |
    | Ctrl + A | 跳转到命令的开始位置 |
    | Ctrl + E | 跳转到命令的结尾位置 |
    | Ctrl + K | 清除从当前位置到命令末尾的所有字符 |
    | Ctrl + R | 向后搜索(输入关键字,重复输入可跳转到下一个) |
    | Ctrl + T | 颠倒当前位置与前一个位置的字符 |
    | Ctrl + L | 清除屏幕 |
    | Ctrl + -> | 以单词为单位跳转 |

  • 单引号中的字符不会被转义,变量也不会被解释;双引号则不同。echo命令的参数最好带上双引号:

    1
    2
    str="four    spaces"
    echo $str # output: four spaces

    原因是$str会被解释成两个字符串,并作为参数传给echo,而加上双引号则指明这是一个字符串。

  • 一些重要的配置文件

    • ~/.bash_profile:用户专有的配置文件,只在用户登录时执行一次,调用~/.bashrc
    • ~/.bashrc:用户专有的配置文件,每次启动bash的时候执行
    • /etc/profile:所有用户登录时执行的文件

Shell编程

  • source *.sh也可以执行程序,其原理是读取每一行并在当前终端执行,而不是创建子进程来执行

  • 优先级从高到低:

    • 别名
    • 关键字
    • 函数
    • 内置命令
    • 脚本和可执行函数
  • 一些变量(只读)

    • $0:脚本名
    • $1:第一个参数
    • $*:所有参数组成的单一字符串,以IFS的第一个字符为分隔符
    • $@:参数列表,即多个字符串,等价于"$1""$2""$3"...
    • $#:参数个数
    • $?:最后一个命令的退出状态
  • 字符串替换操作符

命令 解释
${varname:-word} 返回默认值
${varname:=word} 设置varname默认值
${varname:?message} 为空时输出报错信息,message可以为空
${varname:+word} 若为空,返回word,否则返回空
${varname:offset:length} 返回从offset开始长度为length的子串。:length可省略,则子串一直到末尾。若offset小于0,offset为字符串末尾。若offset@length为从参数offset开始的参数数目
  • 字符串模式匹配操作符
命令 解释
${variable#pattern} 删除开头的最短匹配部分
${variable##pattern} 删除开头的最长匹配部分
${variable%pattern} 删除结尾的最短匹配部分
${variable%%pattern} 删除结尾的最长匹配部分
${variable/pattern/string} 使用string替换匹配到的第一个字符串,pattern中可以以#%开头
${variable//pattern/string} 替换所有的字符串
  • 字符串长度操作符
命令 解释
${<井号>variable} 返回variable的长度,如果variable'yfzm',则返回4
  • if语句的condition的实质是命令的退出状态,类似c语义的条件语句实际上是内置语句test的退出状态。[#condition#]等价于test conditiontest函数的特殊之处在于其退出状态被设置为condition的值(真或假)

  • 判断字符串是否为空的陷阱:

    1
    2
    3
    if [ -n $variable ]; then
    ...
    fi

    这种写法是错误的,如果

    • $variable中有分隔符,将被扩展为多个字符串,导致报出参数过多的错误
    • $variable不存在,语句变为if [ -n ],恒为真

    因此应该使用双引号:

    1
    2
    3
    if [ -n "$variable" ]; then
    ...
    fi
  • 一些常见的操作符

操作符 含义
-n str str非空
-z str str为空
-e file file存在
-d file file存在且为目录
-f file file存在且为普通文件
-[rwx] file file有读/写/执行权限
-s file file存在且非空
-[OG] file 当前用户是file的所有者/当前用户在file的用户组中
file 1 -nt file2 file1file2新(指的是修改时间)
file 1 -ot file2 file1file2
-a &&(仅用于test条件表达式,test中不能用&&
-o ` `
-{lt, le, eq, ge, gt, ne} 算数比较
  • *表示当前目录下的所有文件名,这也是一种模式匹配 (*可以匹配任意字符串)!?也有类似的效果。

  • IFS指定分隔符。

  • getopts示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    # 创建文件名为 filename 的脚本
    usage_prompt="Usage: ${0##*/} [-fh] filename"

    # getopts的第一个参数开始的冒号表示忽略错误输出
    while getopts ':fh' opt; do
    case $opt in
    f ) f_flag=y ;;
    h ) echo "$usage_prompt"
    echo " -f: force to overwrite file"
    echo " -h: help"
    exit 0 ;;
    \? ) echo "$usage_prompt"
    exit 3 ;;
    esac
    done
    shift $(($OPTIND - 1)) # shift已经解析的部分

    # 其它正常的代码
    if [ -z "$1" ]; then
    echo "$usage_prompt"
    exit 1
    fi
    if [ -e "$1" ]; then
    if [ -z "$f_flag" ]; then
    echo "error: file $1 already exists."
    echo "(hint: use -f to overwrite)"
    exit 2
    fi
    rm $1
    fi
    touch $1
    chmod u+x $1
    code $1
  • 算术测试结果和常理相反,如果为真返回0,为假返回1。使用算术测试可以直接使用(( ... )),这样可以避免-ge等符号和对括号的转义。而如果使用$(( ... )),表达式为真时返回1,为假时返回0

  • 使用数组:

    1
    2
    3
    4
    5
    a=(apple [3]=banana pear)
    # a[0]=apple a[3]=banana a[4]=pear
    # a[*]="apple banana pear"
    # a[@]="apple banana pear"
    # #a[@]=3