Qida's Blog

纸上得来终觉浅,绝知此事要躬行。

Q1 参加了一场 Docker 技术分享后,发现了 Docker 这个好东西,回头动手安装,打算跑起来体验一番,却遇到了一些蛋疼的问题。

阅读全文 »

在使用交互式 Bash 时,一个配置得当的命令提示符可以为用户带来不少便利,本文讲解如何配置命令提示符。

默认提示符

命令提示符涉及到以下两个环境变量:

环境变量 描述
PS1 主提示符,Bash 会在准备好读入一条命令时显示,默认值 \s-\v\$
PS2 次提示符,Bash 会在需要更多的输入来完成一条命令时显示,默认值 >

定制提示符

Bash 允许通过插入一些反斜杠转义的特殊字符来定制这些提示符,常用的转义字符如下:

转义字符 描述
\h 主机名,第一个 . 之前的部分
\H 主机名
\j shell 当前管理的作业数量
\l shell 的终端设备名的基本部分
\n 新行符
\r 回车
\s shell 的名称, $0 的基本部分 (最后一个斜杠后面的部分)
\u 当前用户的用户名
\v bash 的版本 (例如,4.3)
\w 当前工作目录
\W 当前工作目录的基本部分
\! 此命令的历史编号
\# 此命令的命令编号
\$ 如果有效 UID 为 0,则显示 #, 否则 $
\\ 一个反斜杠

除此之外,还有一些不太常用的日期转义字符:

转义字符 描述
\d 当前日期,格式是 “星期 月份 日” (例如,”Tue May 26”)
\D{format} 自定义日期格式,花括号是必需的
\t 当前时间,采用 24 小时制的 HH:MM:SS 格式
\T 当前时间,采用 12 小时制的 HH:MM:SS 格式
\@ 当前时间,采用 12 小时制上午/下午 am/pm 格式
\A 当前时间,采用 24 小时制上午/下午格式

如何定制

由于 PS1 默认设置的 \s-\v\$ 实在是太废毫无信息量可言,显示如下:

1
bash-4.3$

因此可以通过修改 ~/.bash_profile 文件来定制自己的命令提示符。例如,使用 CentOS 默认设置的 [\u@\h \W]\$

1
2
3
$ vim ~/.bash_profile

export PS1="[\u@\h \w]\$ "

定制后,能够知道当前用户、主机名、工作目录:

1
[root@BGP-BJ-C-5HL ~]$

参考

http://www.gnu.org/software/bash/manual/bashref.html#Controlling-the-Prompt

自定义shell终端提示符(例如颜色)

brackets cheatsheet

Shell 扩展(Shell Expansions)

命令行的扩展是在拆分成词之后进行的。共有七种类型的扩展:

  • brace expansion
  • tilde expansion
  • parameter and variable expansion
  • command substitution
  • arithmetic expansion
  • word splitting
  • filename expansion

常用的四种如下

花括号扩展(Brace Expansion)

用于偷懒,例如:

1
x{a,b,cd}y

扩展为:

1
xay xby xcdy

再例如:

1
mkdir /usr/local/src/bash/{old,new,dist}

扩展为:

1
2
3
mkdir /usr/local/src/bash/old
mkdir /usr/local/src/bash/new
mkdir /usr/local/src/bash/dist

参数和变量扩展(Parameter and Variable Expansion)

1
${parameter}

算术扩展(Arithmetic Expansion)

1
$(( expression ))

命令替换(Command Substitution)

命令替换允许命令的输出替换命令本身。当命令被以下特殊字符包括时,将发生命令替换:

1
$(command)

1
`command`

参考

https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Shell-Expansions

GUN/Bash 提供了一些内建命令 (BUILTIN COMMANDS),用于在命令行上方便使用:

常用类

echo 显示一行文本或变量

unset 取消变量

set 查看所有变量(环境变量&用户变量)

env 查看所有环境变量(格式好看些)

export

  • 查看所有环境变量
  • 将局部变量转成环境变量:
    • 可以利用 export 命令将局部变量转为环境变量,但是用户注销时值将丢失;
    • 环境配置文件中,经常会用到 export 命令,相当于每次登录时系统都帮用户 export 一下所需环境变量;
    • 环境变量在当前进程 fork 出来的子进程中也能被访问到;
    • 目前发现安装软件时有用。

source.

  • 加载环境配置文件(无须 exit 注销)
  • 执行脚本(在父进程bash中执行,设置的变量都会保留)

declaretypeset

  • -a 定义数组类型
  • -i 定义整数类型
  • -x 将用户变量转成环境变量(与 export 一样)
  • +x 将环境变量降为用户变量
  • -r 定义 readonly 类型

read 读取来自键盘输入的变量

  • -p 后接提示符
  • -t 后接等待“秒数”

test

  • -e 该文件名是否存在(exist)
  • -s 该文件大小是否非 0
  • -z 是否为空字符串(zero)
  • -f 是否为文件(file)
  • -d 是否为目录(directory)
  • -b 是否为块特殊文件(block)
  • -L 是否为连接文件(link)
  • -r -w -x 是否可读、可写、可执行
  • -a -o ! 且、或、非
  • -eq -ne -gt -lt -ge -le (判断2个整数)相等、不等、大于、小于、大于等于、小于等于

[]

  • 中括号 [] 的使用方法与 test 命令几乎一模一样,只是中括号常用于条件判断式 if…then…fi
  • 中括号内的每个元素,都要有空格符分隔
  • 中括号内的变量,最好都以双引号括起来
  • 中括号内的常量(字符串),最好都以单引号 '' 或双引号 "" 括起来

shsh 方式执行,至少需要 r 权限;若以绝对路径方式执行,则需要 rx 权限

  • -n 不执行 script,仅验证语法。若语法无误,则不显示任何信息。(貌似仅能验证关键字错误?)
  • -v 在执行 script 前,先将 script 的内容输出到屏幕上
  • -x 将 script 执行过程逐步输出到屏幕上

作业控制类

Bash 是一个多任务的 CLI ,有以下作业控制(Job Control)相关的命令:

命令 描述
jobs 显示(当前会话中的)后台作业表
fg 将后台作业调到前台执行(前台运行作业)
bg 继续执行指定的后台作业(后台运行作业)
Ctrl+Z 暂停/挂起目前的命令,转入后台运行。通过在命令后追加一个&,可以将该命令转入后台运行
Ctrl+C 终止目前的命令

more bash tricks

参考

http://www.gnu.org/software/bash/manual/bashref.html#Shell-Builtin-Commands

builtins

Shell 命令

简单命令(Simple Commands)

即单个命令。

管道(Pipelines)

bash pipes

pipes

pipeline(管道)是一个或多个命令的序列,用字符 | 分隔。管道的命令格式如下:

1
command [ | command2 ... ]

管道的特点如下:

  • 管道是一个由“标准输入输出”链接起来的进程集合;
  • 管道中的每个命令都作为单独的进程来执行(即在一个子 shell 中启动);
  • 每一个进程的输出(stdout)被直接作为下一个进程的输入(stdin);
  • 管道命令不处理 standard error output(stderr);
  • 管道的符号为:|

管道的处理流程如下图:

pipe

重定向(Redirection)

文件描述符(File Descriptor Number)

文件描述符 名称 描述
0 stdin 标准输入(Standard input)
1 stdout 标准输出(Standard output)
2 stderr 标准错误输出(Standard error)

file descriptors

操作符(Operator)

操作符 描述
< 重定向输入(Redirecting Input)
> 重定向输出(Redirecting Output),与 1> 等价
>> 追加到重定向输出(Appending Redirected Output)
2> 重定向错误输出(Redirecting Error)
2>> 追加到重定向错误输出(Appending Redirected Error)
&> 重定向标准输出和标准错误输出(Redirecting Standard Output and Standard Error)。 推荐使用,它与 >word 2>&1 在语义上等价
>& 同上,但不推荐使用
2>&1 将标准错误输出重定向到标准输出

redirects

在命令执行前,它的输入和输出可能被 redirected (重定向),该功能可以用于如下场景:

  • 屏幕输出的信息很重要,而且需要将它存下来时;
  • 一些运行命令的可能已知错误信息,想以 2> /dev/null 将它丢掉;
  • 一些系统的例行命令(例如写在 /etc/crontab)的运行结果,需要存下来时;
  • 错误信息与正确信息需要分别输出时。

例子:

快速创建带内容的文件:

1
$ echo "hello world" > /tmp/file

stdoutstderr 都重定向到本地日志文件:

1
java -jar app.jar >/tmp/stdout.log 2>&1

stdoutstderr 都丢弃(等价操作):

1
2
java -jar app.jar >/dev/null 2>&1
java -jar app.jar &>/dev/null

序列(Lists of Commands)

list(序列)是一个或多个管道,用操作符 ;&&&|| 分隔的序列, 并且可以选择用 ;&<newline> 新行符结束。

操作符 例子 描述
&& command1 && command2 一个 AND 序列。command2 只有在 command1 返回 0 时才被执行
|| command1 || command2 一个 OR 序列。command2 只有在 command1 返回非 0 状态时才被执行
; command1; command2 结束一个序列。不考虑命令的退出状态,连续执行命令
<newline> command<newline> 结束一个序列
& command1 & 如果一个命令是由 & 结束的, shell 将在后台的子 shell 中执行这个命令
  • AND 和 OR 序列的返回状态是序列中最后执行的命令的返回状态。
  • 这些序列操作符中, &&|| 优先级相同, ;& 优先级相同。

退出状态(Exit Status)

  • 从 shell 的角度看,一个命令退出状态是 0 意味着成功退出。 非零状态值表明失败。
  • 如果没有找到命令,为执行它而创建的子进程返回 127。
  • 如果找到了命令但是文件不可执行,返回状态是 126。
  • 如果命令由于扩展或重定向错误而失败,退出状态大于零。

使用场景

在某些情况下,很多命令我想要一次输入去运行,有两种方法:

  1. Shell Script
  2. 使用序列

例如,一串无人值守源代码形式安装的命令如下:

1
$ ./configure && make && make install

复合命令(Compound Commands)

compound command(复合命令)是如下情况之一:

1

  • (list)
  • { list; }
  • ((expression))
  • [[ expression ]]

if statements

  • if list; then list; [ elif list; then list; ] ... [ else list; ] fi

bash if statements

  • select name [ in word ] ; do list ; done
  • case word in [ [(] pattern [ | pattern ]

select in 通常和 case in 一起使用,在用户输入不同的编号时可以做出不同的反应,见:https://blog.csdn.net/yrx420909/article/details/104308041

for loops

  • while list; do list; done

  • until list; do list; done

  • for name [ in word ] ; do list ; done

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #!/bin/bash

    # 声明一个数组变量
    order_array=(
    10000
    10001
    10002
    )

    # 循环打印与保存到文件
    for id in ${order_array[@]}
    do
    echo "order is $id" | tee -a result.txt
    done
  • for (( expr1 ; expr2 ; expr3 )) ; do list ; done

bash for loops

参考

https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Arrays

https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Shell-Commands

Shell 语法

引用(Quoting)

bash quotes

引用用于:

  • 阻止对特殊字符的处理。
  • 阻止保留字被识别。
  • 阻止参数的扩展。

三种引用机制:

引用符 描述
转义字符 \ 保留其后下一个字符的字面意义
单引号 '' 保留引用中所有字符的字面意义
双引号 "" 保留引用中所有字符的字面意义,例外的情况是 $, `, 和 \

单引号与双引号的使用区别:

quoting

注意,反引号 ` 与单引号 '' 和双引号 "" 作用不同,是用于命令替换(Command Substitution),详见《Shell 常用扩展总结》。

注释(Comments)

# 起始的词使得这个词和所有同一行上所有剩余的字符都被忽略。

Shell 参数(Shell Parameters)

参数(Parameter)是存储值的实体。它可以是以下三类:

  • 变量
  • 位置参数
  • 特殊参数

shell arguments

bash functions

变量(Varialbe)

变量,即用名称(name)表示的参数,其具有值(value)以及零或多个属性(attributes)

  • 通过 $name 引用,在双引号 "" 中可以被引用。

  • 通过以下语句为变量赋值:name=[value]。如果变量未赋值,默认值为 null 字符串。

  • 通过内建命令 unset 为取消变量。

  • 通过内建命令 declare 为变量分配属性(attributes)

所有值都接受以下扩展:

  • tilde expansion
  • parameter and variable expansion
  • command substitution
  • arithmetic expansion
  • quote removal

位置参数(Positional Parameters)

$n$1 表示第一个参数,$2 表示第二个参数,以此类推。

特殊参数(Special Parameters)

$0:表示脚本文件名

$#:表示命令行参数的个数

$?:前一个命令或函数的返回码,0 为成功,非 0 为错误/失败

$*:以”参数1 参数2 … “ 的形式保存所有参数

$@:以”参数1” “参数2” … 的形式保存所有参数

$$:本程序的 PID(进程 ID 号)

$!:最近执行的后台(即异步)命令的 PID

环境变量

environment variables

参考

https://www.gnu.org/software/bash/manual/html_node/Quoting.html

https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Shell-Parameters

了解完什么是 Shell,有哪些 Shell 类型之后,本文开始主要关注 Bash Shell。

启动流程

  1. /bin/login 程序首先会检查 /etc/passwd 文件,在这个文件里包含了用户名、密码和该用户的登录 shell,如 /bin/bash 。 /bin/login 在子进程里用 execve 调用了 /bin/bash 。
  2. /bin/bash 读取 启动文件 并启动
  3. /bin/bash 处理用户键入的命令
  • 在执行磁盘上某个程序时,我们通常不会指定这个程序文件的绝对路径,比如要执行 echo 命令时,我们一般不会输入 /bin/echo ,而仅仅是输入 echo 。那为什么这样 bash 也能够找到 /bin/echo 呢?原因是 Linux 操作系统支持这样一种策略:shell 的一个环境变量 PATH 里头存放了程序的一些路径,当 shell 执行程序时会去这些目录下查找。
  • which 作为 shell(这里特指 bash )的一个内置命令,如果用户输入的命令是磁盘上的某个程序,它会返回这个文件的全路径。

启动文件

交互式 shell

Bash 如何执行它的启动文件?交互式 shell(interactive shell)下需要区分两种情况:

login shell

在下列情况下,我们可以获得一个 login shell:

  • 登录系统时获得的顶层 shell,无论是通过本地终端登录,还是通过网络 ssh 登录。这种情况下获得的 login shell 是一个交互式 shell。
  • 在终端下使用 --login 选项调用 bash,可以获得一个交互式 login shell。
  • 在脚本中使用 --login 选项调用 bash(比如在 shell 脚本第一行做如下指定:#!/bin/bash --login),此时得到一个非交互式的 login shell。
  • 使用 su - 切换到指定用户时,获得此用户的 login shell。如果不使用 -,则获得 non-login shell。

login shell 与 non-login shell 的主要区别在于它们启动时会读取不同的配置文件,从而导致环境不一样。login shell 启动时:

  • 首先读取全局配置:/etc/profile

  • 然后依次查找以下三个文件,读取第一个找到且可读的文件:

    • ~/.bash_profile
    • ~/.bash_login
    • ~/.profile
  • 退出时,读取:~/.bash_logout

no login shell

交互式的 non-login shell 启动时,会读取:

  • ~/.bashrc

通常我们要定制一些配置时(例如 alias 别名),会将配置写在 ~/.bashrc 中,然后在 ~/.bash_profile 中读取 ~/.bashrc,这样可以保证 login shell 和 non-login shell 得到相同的配置:

1
test -f ~/.bashrc && . ~/.bashrc

至于 /etc/profile 就不要轻易去改啦,毕竟会影响系统全局的配置。

非交互式 shell

  • 当 Bash 以非交互的方式(non-interactive shell)启动时,例如在运行一个 shell 脚本时,它会查找环境变量 BASH_ENV,如果存在则将它的值展开,使用展开的值作为一个文件的名称,读取并执行。 Bash 运作的过程就如同执行了下列命令:

    1
    2
    3
    if [ -n "$BASH_ENV" ]; then 
    . "$BASH_ENV";
    fi
  • 其它情况:

    Aliases are not expanded when the shell is not interactive.

    Functions are executed in the context of the current shell; no new process is created to interpret them (contrast this with the execution of a shell script).

参考

Bash Startup Files

Interactive Shells

使用$BASH_ENV来提权