bash 脚本要点(shell)

bash:Bourne Again shell,是 Linux 上的标配 shell;对于想学习 shell 的人来说,无论是新手,还是想进一步提高 shell 编程能力的高级用户,bash 都是比较好的选择。

  • Bash Reference Manual(version 4.4, Sep. 7, 2016);GNU bash;
  • Bash by example @ IBM developerWorks;
  • Advanced Bash-Scripting Guide;
  • Bash Guide for Beginners;
  • Greg's Wiki @ wooledge,有深度,是理解 shell 编程的绝佳资料;
  1. For learning Bash, try the BashGuide.
  2. 引号 Quotes,熟读并测试!
  3. 命令参数 Arguments,熟读并测试!
  4. Word Splitting;
  5. Process Management,有价值!
  • 数据和安全专家 Adam Katz 在 How to get the first line of a file in a bash script? 文章中的回答尽显对 grep、awk、sed 的娴熟掌握。
  • IEEE POSIX Shell Command Language,可以看一下;
  • Bash coding conventions @ stackoverflow;
  • Linux 常用命令;
  • BusyBox;
  • How to determine the current shell I'm working on?
    ps -p $$ -ocomm=:得知 shell 名称;
  • Bash POSIX Mode:尽量遵循 POSIX 标准,以便兼容更多系统;

条件判断

  • Introduction to if
    [ "$a" \> "$b"]字符串比较大小;>< 是重定向字符,做大小比较时,要转义。
  • if TEST-COMMANDS; then CONSEQUENT-COMMANDS; fi
    The TEST-COMMAND list is executed, and if its return status is zero, the CONSEQUENT-COMMANDS list is executed. The return status is the exit status of the last command executed, or zero if no condition tested true.
  • Testing and Branching;elif;
if [  ]; then
...
elif [  ]; then
...
else
...
fi
  • Test Constructs;
    有示例,对于了解 bash 逻辑判断非常有帮助;
  • In bash, how can I check if a string begins with some value?
    检查 $HOST 是否以 node 开头:
case $HOST in node*)
    your code here
esac
  • Variable Assignment(=)
  • string comparison(= or ==)
  • integer and string comparison;
  • Simple logical operators in BASH;
  • Unix Boolean Operators ( &&, -a, ||, -o );
  • $( cd "$( dirname ${0} )" && pwd ) 脚本源文件目录
    Getting the source directory of a Bash script from within【@stackoverflow】;
    How do I determine the location of my script? I want to read some config files from the same place【BashFAQ/028 脚本文件目录】;
    Parameter Substitution 参数替换,字符串查找、替换、截取操作;
    ${var%Pattern}:Remove from $var the shortest part of $Pattern that matches the back end of $var.
    示例:
  1. script_dir=$( cd ${0%/*} && pwd -P ) 文件目录【从右侧开始删除,直到遇到第一个 /:最短删除】
  2. ${0##*/},相当于 "$(basename ${0})" 文件名【从左侧开始删除,直到最后一个/:最长删除】
  3. g_nap=${url##*/}; g_nap=${g_nap%%\?*} 取 url 的 path 的最右侧一节;http://host:port/p1/p2/p3?query,取到的是p3;
  • Extract filename and extension in Bash;
    获取文件的扩展名和文件名:
    filename=$(basename -- "$fullfile")
    extension="${filename##*.}"
    filename="${filename%.*}"

  • How to determine function name from inside a function
    怎么得到 function 函数的名字;

计算赋值
i=0
i=$(expr $i + 1)
i=`expr $i + 1`
i=$(($i + 1))
i=$[$i + 1]
i=$((i + 1))
i=$[i + 1]
  • expr 是一个命令;
  • Command substitution
    Command substitution reassigns the output of a command.
  1. $(command)
  2. `command`
  3. $(...) is preferred over `...` (backticks),建议使用 $(...);
  • 注意:${}$()$[] 的用法。
  • 注意某些嵌入系统要求严格,数字变量初值赋数字,保持可移植性;
  • echo 输出内容使用 "" 双引号括起,某些嵌入系统要求严格;
    类似 echo "who am I: $USER",如果没有双引号,$USER 输出可能就为空;$USER 是一个内置变量;
  • IFS:Internal Field Separator,Input Field Separator;
    The default value of IFS is space, tab, newline. (A three-character string.)
    在 shell 脚本中,一般没有必要修改 IFS。
  • Arithmetic expansion
$(( EXPRESSION ))
$[ EXPRESSION ]
  • 在一行使用 &&; 执行多个命令语句;
    Lists of Commands;list construct;
    Running multiple commands in one line in shell;
    && 表示只有前一条语句执行成功,才执行下一条命令;&& operator to execute the next command only if the previous one succeeded
    ; 表示顺序执行即可。
  • &:后台进程(background);

If a command is terminated by the control operator ‘&’, the shell executes the command asynchronously in a subshell. This is known as executing the command in the background. The shell does not wait for the command to finish, and the return status is 0 (true).

  • [ -z "$name" ][ -n "$name" ] 判断字符串长度,必须双引号括起;
  • [ -s "$name" ][ -d "$name" ] 判断文件空间、目录,必须双引号括起;
  • How to check if running as root in a bash script?
if [ "$EUID" -ne 0 ]; then
  echo "Please run as root"
  exit
fi

变量、函数和引号

  • Quoting Variables;
  • quoting in shell programming is extremely important;
    一定要注重使用引号;
    Why a variable assignment replaces tabs with spaces?
    真实答案是:It isn't the assignment that's losing the tabs, but invoking the echo command.
    这篇文章对 shell 如何解析命令行讲得非常好:command name 命令名,arguments 命令参数,以及 split 分割过程,简洁清晰。
The shell parses your command-line into a command name and a list of arguments. 
It uses white-space (tabs and spaces) to split the command into these parts.
Then it runs the command, passing it the argument list.

Perl one-liner 这个工具也很有用,可以分析参数。Smylers 是个人物。

  • CURL escape single quote;
  • How to add single quote inside of a bash variable?
  • I'm trying to put a command in a variable, but the complex cases always fail!
    Variables hold data. Functions hold code. Don't put code inside variables!
    BashFAQ/050:编程规则之一,记住:使用 set -x 和 set +x 调试 shell script!参见: I want a log of my script's actions;
  • In a shell script: echo shell commands as they are executed
    set -x or set -o xtrace:Options;

错误

  • 127 Return code from $?:命令不存在;
  • reserved exit codes;

双引号

  • Security implications of forgetting to quote a variable in bash/POSIX shells;
  • 参数扩展(Parameter Expansion)、命令替换(command substitution)一定要使用双引号引起来。
    Always use double quotes around variable substitutions and command substitutions;
    "$foo", "$(foo)" 讨论得非常好!

命令

  • Get current users username in bash?
    whoami$USER 查看当前用户
  • source 命令或者 . 命令
    source
    . .命令是POSIX 标准)这里的 . 和 source 一样,都是内置命令;注意区分命令的 . 和表示目录的 .
    source 执行文件时,不要求文件有可执行属性 +x;
    bash 脚本要点(shell)_第1张图片
    source 引入(包含)的文件如何处理 arguments 的?

subshell

  • sh ,不要求 filename 有可执行权限;
  • ./filename,要求 filename 有可执行权限;
  • 内置命令执行shell脚本文件
    shell 内置命令(builtin)不会开启 subshell。
  • command, type, hash
    How to check if a program exists from a Bash script?

awk | gawk

  • Linux 中最常用的命令之一,和 grep、sed 一样重要。
  • BashFAQ/045: How can I ensure that only one instance of a script is running at a time (mutual exclusion, locking)?
    Quick-and-dirty way to ensure only one instance of a shell script is running at a time?
    shell 中如何加锁?
    killall -SIGTERM supertack
  • 随机数 How to generate random number in Bash?
  • 如何计算文本行数
sed -n '$=' filename
awk 'END {print NR}' filename
grep -c '' filename
  • AWK - Built-in Functions @tutorialspoint.com 是一个非常好的 AWK 学习材料,值得从头到尾读一遍,你就是 awk 专家了;
  • how to use awk to manipulate text,learning-awk;
    这是非常好的文章,循序渐进,容易理解和学习使用。
    The basic format of an awk command is:
    awk '/search_pattern/ { action_to_take_on_matches; another_action; }' file_to_parse
    -F,--field-separator 分隔符;
    示例:
    echo "a/b/c" | awk '{print}' | awk '{print $0}'输出的是 a/b/c
    echo "a/b/c" | gawk -F "/" '{print $2}',结果显示 b;
    gawk '/^author/ { print $0 }' onefile,找出文件 onefile 中以 author 开头的行,并打印整行;
  • $(awk -v s="$v" 'BEGIN {gsub("=", "", s); gsub("&", "", s); gsub(" ", "", s); print substr(s, 1, 24)}');
    变量v赋值给s,替换s中的=&空格,仅取s的前24个字符;

命令行参数

  • Getting the last argument passed to a shell script
for last; do true; done
echo $last
  • An example of how to use getopts in bash;
    在 bash 中使用 getopt,指定命令行参数。
如何异步签出代码并构建?

source /home/git/devops/gwph.git.hooks/www.post-receive.gulp >&- 2>&- &

  • 在 post-receive 时启动一个 shell 异步处理页面构建;
    否则 git push 等得时间太长,降低效率;
  • Asynchronous git hook?
  • Asynchronous shell commands
  • How best to include other scripts?
  • How do I parse command line arguments in bash?
何为重定向?

符号 & 比较神奇,在命令后面加 & 就会在后台运行;还可以用来重定向,原来一直对重定向模糊,今天前端构建时要在签入代码之后异步构建页面,看了两篇文章才彻底明白其原理:

  • Bash One-Liners Explained, Part III: All about redirections @ catonmat.net;
    这篇文章配有大量的说明图,一看就明白了;
  • What does “3>&1 1>&2 2>&3” do in a script?
  • How can I redirect and append both stdout and stderr to a file with Bash?
  • How to redirect output to a file and stdout?
    program [arguments...] 2>&1 | tee outfile:将 program 的 stderr 重定向到 stdout,通过 | 定向到 文件 outfile 中。
  • &> 是 bash 的语法;
if cmp a b &> /dev/null  # Suppress output.
then echo "Files a and b are identical."
else echo "Files a and b differ."
fi
  • ./sapiloader >/dev/null 2>&1 & 后台运行,所有的错误和输出都不再到屏幕;
    In the shell, what does “ 2>&1 ” mean?
  • see REDIRECTION in bash man;
bash shell 特殊变量
  • Special Variables;
  • Special Variable Types;
  • 8类特殊变量
No. Variable Description
1 $0 The filename of the current script.
2 $n These variables correspond to the arguments with which a script was invoked. Here n is a positive decimal number corresponding to the position of an argument (the first argument is $1, the second argument is $2, and so on).
3 $# The number of arguments supplied to a script.
4 $* All the arguments are double quoted. If a script receives two arguments, $* is equivalent to $1 $2.
5 $@ All the arguments are individually double quoted. If a script receives two arguments, $@ is equivalent to $1 $2.
6 $? The exit status of the last command executed.
7 $$ The process number of the current shell. For shell scripts, this is the process ID under which they are executing.
8 $! The process number of the last background command.
  • 在 shell 命令行下,echo $$ 即可知当前 shell 的 PID 值。
  • Understanding Exit Codes and how to use them in bash scripts;

The exit command in bash accepts integers from 0 - 255, in most cases 0 and 1 will suffice, however there are other reserved exit codes that can be used for more specific errors. The Linux Documentation Project has a pretty good table of reserved exit codes and what they are used for.
保留错误码。错误码在 1-255 之间。用户使用的话,建议在 1-125 之间取值。

  • Return value in a Bash function;
  • Difference between return and exit in BASH functions;
备注
  • source:How to include file in a bash shell script?
    source *filename* [arguments]

Read and execute commands from filename in the current shell environment and return the exit status of the last command executed from filename.
If any arguments are supplied, they become the positional parameters when filename is executed.
Otherwise the positional parameters are unchanged.
The return status is the status of the last command exited within the script (0 if no commands are executed),
and false if filename is not found or cannot be read.

字符串比较请使用一个等号=即可(POSIX标准)
[ STRING1 == STRING2 ]  True if the strings are equal. "=" may be used instead of "==" for strict POSIX compliance.
bash 脚本要点(shell)_第2张图片
使用superctl_ok.sh更合适

BASH 基本概念

bash 脚本要点(shell)_第3张图片
Bash Component Architecture @ aosabook.org
Brace Expansion:{} 扩展;
Tilde Expansion:~ 扩展;
Variable and Parameter Expansion(PE):变量和参数扩展;
  • 理解 Parameter Expansion 这个概念很重要;
  • 理解:literal 和 syntactic,尤其:空白符引号反斜线 在何时是 syntactic?
  • Variables are a common type of parameter.

It is vital to understand, however, that Quoting and Escaping are considered before parameter expansion happens, while Word Splitting is performed after. That means that it remains absolutely vital that we quote our parameter expansions, in case they may expand values that contain syntactical whitespace which will then in the next step be word-split.

  • Command, Process, Arithmatic Substitution:命令、进程、算数替换;
  • Word Splitting:分词,Field Splitting;理解 IFS;
  • Filename Generation:文件名生成;
  • Shell Expansions:基础知识,值得读,详细解释了以上各种扩展和替换;
解析流程
  • arguments 命令参数分析,对 shell 和 系统调用 的关系讲得清楚;
  • Processing the Command Line by Mark G. Sobell and Peter Seebach;
  • Command-line Processing;
    bash 脚本要点(shell)_第4张图片
    Steps in Command-line Processing
  • How shell processes the content of command line in order to execute?
  • Command not found error in Bash variable assignment;
eval 050 规则
  • Eval command and security issues,基础知识,值得读;
  • Execute command containing quotes from shell variable [duplicate];
  • Why does shell ignore quotes in arguments passed to it through variables?这个帖子讲得比较清楚;
  • Execute a command stored in a variable,对 eval 有讲述;
  • Stéphane Chazelas;
  • How to assign space-containing values to variables in bash using eval?
    A good way to work with eval is to replace it with echo for testing. echo and eval work the same.
  • Unable to set variables in bash script [duplicate]:Jahid 和 Ignacio 的回答都不错;
  • Why is printf better than echo?
  • Assignment of variables with space after the (=) sign?
    PWD= /bin/pwd,PWD变量所赋值(本例是空值)只在 /bin/pwd 执行期间有效,或者说只应用于 /bin/pwd 命令;
    IFS=, read -ra namesArray <<< "$names":IFS临时设置为,,只在 read 期间有效;这种用法 只对几个命令有效,read 是其中之一;一般不要修改 IFS 变量设置;
    how about name=hello worldworld: command not found
  • shell解析命令行的过程以及eval命令;这篇博文不错,博主写的大都是运维类文章;
Greg's Wiki【wooledge.org】
  1. 首先理解系统调用 execve
    int execve(const char *filename, char *const argv[], char *const envp[]);
    argv:argument vector;
    envp:environment;
  2. 理解 shell 如何将 命令 command 翻译成系统调用;
  3. 实现业务逻辑;
Quote Guidelines
  • quoting in shell programming is extremely important.
  • "Quote" any arguments that contain data which also happens to be shell syntax.
  • "$Quote" all parameter expansions in arguments. You never really know what a parameter might expand into; and even if you think it won't expand bytes that happen to be shell syntax, quoting will future-proof your code and make it safer and more consistent.
  • Don't try to put syntactical quotes inside parameters. It doesn't work.
了解 shell
  • Bash Style Guide and Coding Standard;
  • BASH Debugger;
  • Shell types;
  • sha-bang(Shebang,hashbang);
知识点
  • 文件大小
    ls -l filename |awk '{print $5}'
    wc -c < filename:short for word count, -c prints the byte count. wc is a portable, POSIX solution.
    du -k filename | cut -f1
  • cat <
    How does “cat << EOF” work in bash?

示例:
cat < Usage: $0 [options]
Language-agnostic unit tests for subprocesses.
Options:
-v, --verbose generate output for every individual test case
-h show brief usage information and exit
--help show this help message and exit
EOF

  • 查看 bash 版本
    /bin/bash --version
    echo $BASH_VERSION

sudo bats 时报告sudo: bats: command not found

$ sudo bats sapiloader.bats
sudo: bats: command not found

解决方案:

The error happens because the binary you are trying to call from command line is only part of the current user's PATH variable, but not a part of root user's PATH.
$ sudo env | grep ^PATH 查看 sudo 的 PATH,果然发现不包含 /usr/local/bin,因此:将 bats 改为全路径 /usr/local/bin/bats 就可以正常执行,但不方便,我们改造 ~/.bashrc,加一条语句,创建 sudo 别名(别名优先于命令)即可:
alias sudo='sudo -E env "PATH=$PATH"'
完美解决!

  • Command not found when using sudo;

备注

  • 错误 [: bad number 的问题原因是:[ $EUID -eq 0 ] 语句中,EUID 没有赋值,在有些 shell 中 EUID 是内部变量,会自动赋值,你在 script 中使用即可,但有些就没有赋值,所以不要使用。但从用法上可以这么解决:${EUID:-0},采用 Parameter Substitution default 值来避免错误,当 EUID 未声明或者赋值为空时,输出 0。

你可能感兴趣的:(bash 脚本要点(shell))