# 命令

# 命令格式

一条 shell 命令的基本格式为 <命令名> <option> <argument>

  • 每个字段之间用空格隔开(多个空格也看作一个空格),区分大小写。
  • 在命令的用法说明中,通常用尖括号 <> 表示必填字段,用方括号 [] 表示可选字段,用省略号 ... 表示该字段可以输入多个。如下:
    ls [OPTION]...
    ping [-c count] [-i interval] destination
    
  • 命令名可以是某条内建命令的名字、某个已安装软件的名字,或者某个可执行文件的路径(绝对路径或相对路径)。
  • option 用于选择该命令的一项功能,有长格式(比如 --option )、短格式(比如 -o )两种写法。如下:
    • 有些短格式的命令行选项可能与某个长格式选项等价,比如 -h 与 --help 。但有些命令只有 --help 选项,没有 -h 选项;有些命令有 -h 选项,但不等价于 --help 选项。
    • 多个命令行选项可以组合使用,且不要求顺序,比如 ls -a -l 相当于 ls -l -a 或 ls -al
  • argument 可能是传给命令的参数,也可能是传给命令行选项的参数。
    • 给命令行选项传入参数时,可以用空格或等号作为分隔符,比如 head -n 3 filehead -lines=3 file

# 执行命令

  • 在终端执行一条命令,实际上是执行一个程序来实现某种操作。

    • 该程序默认是终端的子进程。
    • 输入的命令参数会传给该程序,控制该程序的行为。
    • 程序执行结束之后会返回一个退出码。
  • 执行一条命令时,系统会按以下顺序找到一个对应的程序来执行:

    1. 查找命令名是否为一个有效的文件路径(绝对路径或相对路径),如果是则执行该文件。
    2. 查找命令名是否为 alias 定义的别名。
    3. 查找命令名是否为 shell 的内建命令。
    4. 到 PATH 路径下查找与命令名同名的可执行文件。
    5. 如果始终没有找到,则报错:command not found

# PATH

  • 环境变量 PATH 记录了一些可执行文件的保存路径,每个路径之间用冒号 : 分隔。如下:
    [root@CentOS ~]# echo $PATH
    /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    
  • Linux 系统的 PATH 默认不包含当前目录 . ,因此运行当前目录下的可执行文件时要加上 ./ 前缀。如下:
    [root@CentOS ~]# 1.sh
    -bash: 1.sh: command not found
    [root@CentOS ~]# ./1.sh
    hello
    
  • 例:添加路径到 PATH 中
    [root@CentOS ~]# export PATH=$PATH:/root/bin
    

# 执行多条命令

  • 一般每条命令独占一行,也可以用分号 ; 标明一条命令的结束,从而在同一行内输入多条命令。如下:

    [root@CentOS ~]# x=1; echo $x
    1
    
  • 也可以用 &&|| 连接多条命令,这样会当前一条命令执行成功或失败时才执行后一条命令。如下:

    [root@CentOS ~]# ls /root/f1 && echo True || echo False
    ls: cannot access /root/f1: No such file or directory
    False
    
  • 在行尾输入反斜杠 \ ,就可以将一条命令换行输入。如下:

    [root@CentOS ~]# echo hello \
    > world
    hello world
    

# 命令的输入、输出

# echo

  • echo 命令用于将字符串显示到当前终端。用法:
    $ echo <string>...   # 让终端回显字符串
          -n             # 显示之后不自动换行
          -e             # 解析转义字符。默认不会解析
    
  • 例:
    [root@CentOS ~]# echo hello             # 显示字符串
    hello
    [root@CentOS ~]# a=1 && echo $a         # 提取变量的值再显示
    1
    [root@CentOS ~]# echo -n hello          # 显示之后不自动换行
    hello[root@CentOS ~]#
    [root@CentOS ~]# echo -e "hello\n\x41"  # 解析转义字符
    hello
    A
    

# yes

$ yes [string]    # 持续不断地输出一个字符串(默认是 'y' )到 stdin
  • 例:
    yes hello
    yes | yum install git
    

# 重定向

  • 一般的命令都是从 stdin 读取输入,并将输出写到 stdout ,将报错写到 stderr 。

  • 通过重定向 stdin、stdout、stderr 的文件描述符,可以转移终端的输入输出。如下:

    echo hello >  stdout.log    # 用 > 将 stdout 重定向到指定文件,相当于 1>
    echo hello >> stdout.log    # 用 >> 是追加式写入
    echo hello 2> stderr.log    # 2> 是重定向 stderr
    echo hello &> output.log    # &> 是重定向 stdout 和 stderr
    echo hello 2>&1             # 将 stderr 重定向到 stdout
    echo hello 1> stdout.log  2> stderr.log    # 分别重定向 stdout、stderr
    echo hello 3>&1 1>&2 2>&3   # 创建一个文件描述符 3 ,将它重定向到 1 。然后将 1 重定向到 2 ,将 2 重定向到 3 。这样就互换了 stdin 和 stdout
    
    cat < stdout.log            # 用 < 将指定文件重定向到 stdin ,即读取该文件的内容作为输入,读取到 EOL 为止
    cat <& 0                    # 加上 & ,将指定 ID 的文件描述符重定向到 stdin
    cat <<EOF ... EOF           # 以 EOF 作为定界符(可以换成其它符号),将一些内容重定向到 stdin
    
    • 使用 > 或 >> 时,
      • 如果目标文件不存在,则会自动创建它。
      • 如果在命令执行时将目标文件删掉,则不会重新创建该文件,因此不会保存重定向之后的输出。
    • 使用 > 时,
      • 如果目标文件已存在,则会先将其长度截断为 0 ,相当于覆盖式写入。
  • 例:

    [root@CentOS ~]# cat > f1 <<EOF
    >Hello
    >World
    >EOF
    
  • 同时重定向多个文件描述符时,是按从右往左的优先级:

    [root@CentOS ~]# echo1 >f1 2>&1   # 这是先将 stderr 重定向到 stdout ,再将 stdout 重定向到 f1
    [root@CentOS ~]# echo1 2>&1 >f1   # 这是先将 stdout 重定向到 f1 ,而 stderr 输出到了终端
    -bash: echo1: command not found
    

# 管道符

使用管道符 | 可以在连续执行两条命令时,将上一条命令的 stdout 转化成下一条命令的 stdin 。

  • 例:
    cat /etc/*-release | grep Linux
    

# xargs

有些命令默认会读取 stdin ,因此可以通过管道符传递上一条命令的输出;有些命令默认不会读取 stdin ,此时可使用管道符加 xargs 命令。

  • 命令:
    $ xargs [command]   # 从 stdin 中提取内容,传给子命令去执行(子命令默认为 echo )。每传递一行内容,就执行一次子命令
            -d ','      # 提取内容时的字段分隔符。默认为一个或多个连续的空格、Tab
            -n 3        # 每提取 3 个字段就换行
            -L 3        # 每提取 3 行,就合并为一行。默认全部合并为一行
            -I {}       # 默认会将提取的内容拼接到子命令末尾,而使用 -I 选项会进行字符串替换
            -P 1        # 并发进程数,允许同时执行多少个子命令。默认为 1
    
  • 例:
    [root@CentOS ~]# echo A B | xargs
    A B
    [root@CentOS ~]# echo A B | xargs -n 1 echo
    A
    B
    [root@CentOS ~]# ls /root/ | xargs -n1 -P4 -I{} rsync -aP /root/{} /tmp/  # 并行拷贝
    

# tee

$ tee <file>...    # 将 stdin 的内容同时拷贝到 stdout 和一个文件中
        -a         # 采用追加模式写入(默认采用覆盖模式写入)
  • 例:
    [root@CentOS ~]# echo Hello | tee f1                # 类似于 echo hello > f1 ,但是依然会输出 stdout
    Hello
    [root@CentOS ~]# echo Hello | tee - | wc -l         # tee - 是输出到 /dev/stdout ,因此这里输出两份到 stdout
    2
    [root@CentOS ~]# echo Hello | tee /dev/tty | wc -l  # 这里让 tee 输出到当前终端,不经过 stdout
    Hello
    1
    

# 关于命令的命令

# alias

$ alias               # 显示所有已定义的命令别名
        <name>        # 显示某个别名的定义语句
        cd1="cd ~"    # 定义一个别名
  • 将 alias 的定义语句写到 /etc/bashrc 中就可以永久生效,如下:
    alias ll='ls -lh --color=auto'
    alias ls='ls --color=auto'
    alias cp='cp -i'
    alias rm='rm -i'
    

# unalias

$ unalias <name>      # 取消一个别名

# man

$ man <name>          # 显示命令的使用手册(manual),这会打开 less 阅读器

# type

$ type <name>...      # 显示指定命令名的类型(还会显示该命令名的二进制文件的位置)

# which

$ which <name>...     # 显示指定命令的可执行文件的绝对路径(在 PATH 路径下寻找,显示第一个找到的)
        -a            # 显示指定命令的所有可执行文件
        -i            # 考虑 alias 别名

# whereis

$ whereis <name>...   # 显示指定命令的可执行文件、源代码、manual 文件的绝对路径(在 PATH、MANPATH 路径下寻找)

# history

$ history         # 显示所有历史命令
          [n]     # 只显示最近的 n 条
          -c      # 清空当前终端的历史命令,因此它们不会保存到 HISTFILE
          -r      # 读取 HISTFILE 的内容,作为当前终端的历史命令
          -a      # 将当前终端中新增的历史命令追加写入 HISTFILE
          -w      # 将当前终端的全部历史命令写入 HISTFILE ,覆盖原内容
  • 用户每次打开一个交互式终端时,shell 会从 HISTFILE 加载历史命令到内存中,并记录当前终端新增的历史命令。

    • 当终端关闭时,shell 会自动执行 history -a ,保存新增的历史命令。可执行 unset HISTFILEhistory -c 避免保存。
    • HISTFILE 文件可能被删除、篡改,可信度不高。
  • 相关的环境变量:

    HISTFILE=~/.bash_history    # 保存历史命令的文件
    HISTFILESIZE=1000           # 最多记录多少条命令
    HISTSIZE=1000               # 执行 history 命令时最多显示多少条命令
    HISTTIMEFORMAT="%F %T  "    # 执行 history 命令时,在每条命令之前显示时间戳
    HISTCONTROL=ignoredups      # 如果几条连续执行的命令是重复的,则只记录第一条
              # ignorespace     # 不记录以空格开头的命令
              # ignoreboth      # 不记录连续重复的命令,或以空格开头的命令
    PROMPT_COMMAND="history -a" # 每次显示终端提示符之前,执行一次该命令
    

    例:

    [root@CentOS ~]# export HISTTIMEFORMAT="%F %T  `whoami`  "
    [root@CentOS ~]# history 3
    1031  root  2020-07-08 15:20:05  ls
    1032  root  2020-07-08 15:20:16  export HISTTIMEFORMAT="%F %T  `whoami`  "
    1033  root  2020-07-08 15:20:17  history 3
    
  • 可通过以下快捷方式执行历史命令:

    !!         # 打印上一条命令的内容,并执行它
    sudo !!    # 以 root 用户的身份执行上一条命令
    !n         # 执行历史记录中的第 n 条命令
    !$         # 获得上一条命令的最后一个参数