小时百科
    百科
    讨论版
    AI 问答
    用户
    云笔记

    评论列表

    贡献者:addis

    Bash 编程笔记

                         

    • 本文处于草稿阶段。

    1. 基础

    • 一个教程
    • 一个比较笨的调试方法是,把一些要调试的命令用 echo "..." 先输出来看看,而不是真的执行。看看变量展开是否和预期的一致(就像 make 一样)。
    • Linux 系统中,shell 一般是指 Bash(Bourne Again SHell)
    • shell script 就是拓展名为 .sh 的文本文件,也可以没有。
    • 执行 shell script 相当于执行可执行程序,可以用 ./name.sh 参数1 参数2 也可以用 source /path/name.sh 参数1 参数2source 也可以简写成 .sh shell 中只能用后者。), 前者在一个新的 shell process 中运行然后退出(环境变量之类的改动在退出会消失), 后者在当前的 shell process 运行, 相当于直接把文件中的内容复制粘贴到命令行执行.
    • ; 隔开两个命令,第一个失败了第二个仍然会运行,但 && 隔开则不会(这个其实是逻辑与算符)。
    • 所有变量都是字符串
    • && 后面可以直接换行,不需要加 \
    • shell script 后面可以输入若干 arguments, 还可以用 > 把 shell 中的 stdout 导入文件.
    • 文件第一行可以用 shebang #! 指定运行使用的 shell,如 #!/bin/bash
    • exit 结束 script,exit 0 声明运行成功
    • # 注释
    • 赋值用 var=xxx,等号两边不能有空格。$var 或者 ${var} 获取它的值。例如 abc$var 或者 abc${var}def。后者的花括号不能省略。
    • ${var:2:5} 可以获取第 3 个字符开始的 5 个字符。
    • ${var/旧文字/新文字} 可以对变量的字符串进行替换
    • ${var:-"默认字符串"} 如果 var 未定义或为空,则输出 默认字符串
    • 如果变量 var字符串 结束,${var%字符串} 移除最后的 字符串
    • 如果 var字符串 开始,${var#字符串} 移除最前面的 字符串
    • 要把变量的内容作为另一个变量的名字展开,如 varname=myvar,那么 ${!varname} 相当于 ${myvar}。如果 varname 是第一个参数,那么就用 ${!1}
    • $(命令) 或者 `命令` 相当于把 命令 的输出展开到当前位置,如 echo `pwd -P`
    • 赋值给变量时,双引号中的多个空格会变为一个:bins="123 234"
    • 脚本中用 exec 文件名.sh 执行另一个脚本。
    • 如果要用脚本输出 stderr,在命令后面加 1>&2,例如 echo bar 1>&2
    • 同理,./文件名.sh > 123.log 只会把 stdout 导入文件,而不包含 stderr。还要在后面加上 2>&1
    • 要在一个 script 中插入另一个,用 "path/to/other_script.sh"(待验证)
    • 在其他 shell 中,要使用 bash 执行单个命令(而不是进入 bash)用 /bin/bash -c "命令"

    关于引号

    • 命令 "abc def" 或者 命令 'abc def' 中,abc def 是一个参数,作为字符串传给 main() 函数的 argv[1],不包含引号。如果不加引号,abcdef 就分别是 argv[1]argv[2]
    • "..." 中的 $变量 会展开,但是 '...' 中不会
    • '...' 就是绝对的 string literal,里面不会发生任何转义
    • 要在 '...' 中 escape 单引号,不能直接做到,可以用 'it'\''s here.' 其中三部分分别是 'it'\''s here.'
    • echo 'abc\'def'gh' 会输出 abc\defgh
    • 单引号和双引号中间都可以换行。
    • 如果参数中出现了引号想要传给 main(),那么就 escape:\" 或者 \'
    • 不在引号内的 \ 可以用 \\ 来 escape
    • 最外层引号内部的所有其他符号都是 raw string,不需要 escape。
    • 双引号中可以用 $var${var} 等展开。

    2. pipe

    • 命令 | 命令2 使用 pipe 把一个命令在 stdout 的输出作为另一个命令的 stdin 输入。特别注意 命令2 是在 subshell 执行的。如果在该 subshell 执行多个命令,可以用 命令 | { 命令2 ; 命令3 } 例如 echo 123 | { read line; echo "The line is: $line"; } 又例如 printf "123\n234\n" | while read line; do echo "The line is: $line"; donewhile 可以看成一个命令。

    3. 判断

    echo 123;
    if [ $? -ne 0 ]; then # 判断 exit status
    ...
    fi
    
    # 判断路径是否存在(感叹号后面一定要有空格)
    if ! [ -d "$repo" ]; then
        echo "directory not found!" 1>&2
    fi
    
    # 判断文件是否存在且不是文件夹或者特殊文件
    if ! [ -f "$fname" ]; then
        echo "file not found!" 1>&2
    fi
    
    # 判断相等
    if [ $i -eq 3 ]
        ...
    elif [ $i -ne 3 ]
        ...
    fi
    

    • 一种等效于 if 的简写如 [ -e 文件 ] && mv 文件 新文件。例子:for file in file1 file2 file3; do [ -e "$file" ] && mv "$file" destination_directory done
    • 类似于 -f, -d,还有 -z(判断字符串是否为空),-n(判断字符串是否为非空),例如 if [ -n $(git remote | grep github) ]-e 判断是否是文件(包括文件夹和特殊文件),-h 判断是否为 symlink,file1 -nt file2(newer than),file1 -ot file2(older than),-r, -w, -x 是否是文件且具有读写执行权限。
    • if [ string1 = string2 ] 判断字符串是否相等,string1 != string2 判断是否不相等。
    • 中括号中:-z 检查后面的内容是否为空.
    • -n 检查是否为非空
    • -v 检查变量是否有定义(定义为空字符也算)

    双方括号判断

       双方括号不属于 POSIX 标准而是 bash 和 zsh 等特有的。语法更为现代

    • if [[ $a == $b ]]; then 判断相等,另有 !=, <, > 等。注意 <= 注意比较的不是数值大小而是字符串排序大小。
    • if [[ "字符串" =~ 正则表达式 ]] 可以判断字符串是否匹配正则表达式。其中正则表达式的格式和 grep -E 中的是一样的。例如 if [[ "$var" =~ ^-?[0-9]+$ ]] 判断变量是否表示整数。
      # 判断文件是否为空
      if [ -s 文件名 ]; then
          # The file is not-empty.
      else
          # The file is empty.
      fi
      
      # 比较数字大小
      if (( a > b )); then
          ...
      fi
      

    4. 循环

    #!/bin/bash
    for ((i = 1; i < 5; ++i))
    # for i in {1,2,3,4} # 等效
    # for i in 1 2 3 4 # 等效
    do
    	printf "\nfile${i}.txt\n"
    done
    
    如果在 printf 语句后面加上 && break,当前者失败时就会跳出循环。

       另外 continue 也可以跳出循环

       在当前目录中所有子目录循环

    for d in */ ; do
        d=${d%/} # 删除目录名最后的斜杠
        echo "$d" # 打印所有子目录名
    done
    
    文件循环
    for file in *; do
        if [ -f "$file" ]; then
            echo "$file"
        fi
    done
    

    5. 数组

    #!/bin/bash
    arr=(1 3 hello 13)
    for ((i=0;i<4;++i))
    do
        printf "arr[${i}] = ${arr[i]}\n"
    done
    

    6. 输入

       例子

    #!/bin/bash
    # 确保文件最后有两个空格
    # use `find . -type f -name "*.matt" -exec ./convert_matt.sh {} \;`
    # to convert subfolder
    file=${1}
    printf "${file}... "
    c2=$(tail -c 2 ${file})
    if [ "${c2}" == "  " ]
    then
    	printf "already in new format\n"
    else
    	printf "  " >> ${file}
    	printf "done\n"
    fi
    

    7. 特殊变量

    • $0 当前 shell script 的文件名,例如调用脚本的命令是 some/../path/test.sh,那就返回这串字符。如果用 source some/path/test.sh 或者直接在命令行打 $0,那么就返回 bash
    • $@ 返回所有 shell arguments
    • $? 上一个命令的 exit status0 代表成功,否则失败
    • readlink -f "$0" 获取脚本绝对路径(包括脚本文件名)(不可以直接在命令行使用,或者 source 脚本.sh 时使用)
    • dirname $(readlink -f "$0") 获取脚本所在绝对目录(不包括脚本文件名)

    8. 函数

    • 函数的定义为
      函数名() {
      	...
      }
      
    • 需要 argument 的话,就用 $1, $2 或者 ${1}, ${2},例如
      greeting() {
        echo "Hello $1"
      }
      
      greeting "Joe"
      

       $0 是调用脚本的命令(也就是 argv[0])。$# 可以查看 args 的总个数

       一个用于确保执行成功的函数:

    check_failure() {
        if [ $1 -ne 0 ]; then
            echo "Error! Exiting..."
            exit 1
        fi
    }
    
    cmd1 arg1 && cmd2 arg2
    check_failure $?
    

    9. 获取系统信息

    • lsb_release -rs 可以获取 ubuntu 的系统版本号(例如 18.04
    • getconf _NPROCESSORS_ONLN 可以查看系统有多少逻辑 cpu

    致读者: 小时百科一直以来坚持所有内容免费无广告,这导致我们处于严重的亏损状态。 长此以往很可能会最终导致我们不得不选择大量广告以及内容付费等。 因此,我们请求广大读者热心打赏 ,使网站得以健康发展。 如果看到这条信息的每位读者能慷慨打赏 20 元,我们一周就能脱离亏损, 并在接下来的一年里向所有读者继续免费提供优质内容。 但遗憾的是只有不到 1% 的读者愿意捐款, 他们的付出帮助了 99% 的读者免费获取知识, 我们在此表示感谢。

                         

    友情链接: 超理论坛 | ©小时科技 保留一切权利