65.9K
CodeProject 正在变化。 阅读更多。
Home

有效 Shell 第 7 部分:Shell 命令的细微差别

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2019年6月25日

CPOL

9分钟阅读

viewsIcon

5133

Shell 命令的细微之处

Effective Shell Part 7: The Subtleties of Shell Commands

在本章中,我们将探讨存在的各种不同类型的shell命令,以及这如何影响你的工作。

在本章结束时,你甚至可能能够理解下面这段恐怖但语法完全有效的代码

which $(where $(what $(whence $(whereis who))))

什么是命令?

理解这一点非常重要!shell 中的命令是你执行的任何东西。它可能接受参数。通常,它的形式如下

command param1 param2

在这个系列中,我们已经见过很多命令了

ls              # Show the contents of the current directory
cd ~            # Move to the user's home
cat file.txt    # Output the contents of 'file.txt' to stdout

但是要成为一个有效的 shell 用户,你必须理解并非所有命令都是平等的。命令类型之间的差异会影响你使用它们的方式。

在大多数 shell 中有四种类型的命令

  1. 可执行文件
  2. “内置命令”(我们以后将只称之为内置命令
  3. 函数
  4. 别名

让我们快速深入了解一下。

可执行文件 - 程序

可执行文件只是设置了“可执行”位的[1]文件。如果我执行 `cat` 命令,shell 将在我的 `$PATH` 中搜索一个名为 `cat` 的可执行文件。如果找到,它将运行该程序。

$ cat file.txt
This is a simple text file

什么是 `$PATH`? `$PATH` 是用于定义 shell 应该在哪里搜索程序的标准环境变量。如果我们暂时清空这个变量,shell 就找不到命令了

$ PATH="" cat file.txt
bash: cat: No such file or directory

通常,你的 `$PATH` 变量将包含 Linux 程序[2]的标准位置 - 例如/bin/sbin/usr/bin等文件夹。

如果你打印变量,你会看到一堆路径(它们用冒号分隔;为了便于阅读,我将它们放在单独的行上)

/usr/local/bin
/usr/bin
/bin
/usr/sbin
/sbin

shell 将从靠前的位置开始,然后移到靠后的位置。这允许为用户安装本地版本的工具,这些工具将优先于通用版本的工具。

可能还会有其他位置 - 你可能会看到 Java 文件夹、包管理器文件夹等。

可执行文件 - 脚本

假设我们在本地文件夹中创建一个名为 `dog` 的文本文件

#!/bin/sh
echo "🐶 woof 🐶"

如果我们通过运行 `chmod +x dog` [3]使文件可执行,那么我们就可以像运行任何其他程序一样运行它 - 只要我们告诉 shell 在当前目录中查找程序

$ PATH="." dog
🐶 woof 🐶

更常见的是通过提供路径来运行程序

$ ./dog
🐶 woof 🐶

或者干脆将其移动到 shell 已经检查程序的标准位置

$ mv dog /usr/local/bin
$ dog
🐶 woof 🐶

关键在于,可执行文件不必是编译过的程序代码。如果一个文件以 `#!`(“shebang”)开头,那么系统将尝试使用 shebang 中指定的程序来运行文件内容。

我们将在后面的章节中更详细地介绍 shebang。

内置命令

好的,我们已经看到了可执行文件。那么像这样的命令呢?

local V="hello" echo $V

你不会在系统上的任何地方找到 `local` 这个可执行文件。它是一个内置命令 - 直接内置于 shell 程序中的特殊命令。

内置命令通常与你的 shell 高度相关。它们可能用于编程(例如 `local` 用于声明局部作用域的变量),或者可能用于非常 shell 特定的功能。

这是我们需要注意的地方。一旦你运行了一个内置命令,你就可能在使用一个特定于你的 shell 的功能,而不是一个在系统范围内共享并且可以被任何 shell 运行的程序。

尝试以编程方式执行 `local` 作为进程将会失败 - 没有名为 `local` 的可执行文件;它纯粹是 shell 的构造。

那么我们如何知道一个命令是否是内置命令呢?首选方法是使用 `type` 命令

$ type local
local is a shell builtin

`type` 命令(它本身也是一个内置命令!)可以告诉你 shell 命令的确切类型。

有趣的是,你可能比你想象的使用了更多的内置命令。 `echo` 是一个程序,但大多数时候你在 shell 中运行时并没有执行它

$ type -a echo
echo is a shell builtin
echo is /bin/echo

通过在 `type` 上使用 `-a` 标志来显示匹配名称的所有命令,我们看到 `echo` 实际上既是内置命令也是一个程序。

许多简单的程序都有内置版本。shell 可以更快地执行它们。

一些命令是内置的,以便它们能够以有意义的方式运行。`cd` 命令用于更改当前目录 - 如果我们将其作为进程执行,它只会更改 `cd` 进程本身的目录,而不是 shell,这使得它用处不大。

内置命令会因 shell 而异,但许多 shell 是“类 Bash”的 - 也就是说,它们将具有与 Bash 内置命令非常相似的集合,你可以在这里看到

正如你在 第 3 部分:获取帮助 中所熟悉的那样,你可以为内置命令获取帮助

$ man source     # source is a builtin
BUILTIN(1)                BSD General Commands Manual               BUILTIN(1)

NAME
     builtin, !, %, # ...snip...

SYNOPSIS
     builtin [-options] [args ...]

但是,手册不会显示特定内置命令的信息,这很麻烦。你的 shell可能有一个选项可以显示更多详细信息 - 例如,在 Bash 中你可以使用 `help`

$ help source
source: source filename [arguments]
    Read and execute commands from FILENAME and return.  The pathnames
    in $PATH are used to find the directory containing FILENAME.  If any
    ARGUMENTS are supplied, they become the positional parameters when
    FILENAME is executed.

但请记住:`help` 是一个内置命令;你可能在所有 shell 中都找不到它(例如,在 `zsh` 中就找不到)。这再次凸显了内置命令的挑战。

函数

你可以定义自己的 shell 函数。我们稍后会看到更多这方面的内容,但现在我们先举一个简短的例子

$ restart-shell () { exec -l $SHELL }

这段代码创建了一个函数,该函数可以重启 shell(如果你正在修改 shell 配置文件或认为你可能已不可逆地弄乱了当前会话,这非常有用)。

我们可以像运行任何命令一样执行此函数

$ restart-shell

运行 `type` 将显示这是一个函数

$ type restart-shell
restart-shell is a function
restart-shell ()
{
    exec -l $SHELL
}

函数是我们将会看到的 most 强大的 shell 构造之一;它们对于构建复杂的逻辑非常有价值。我们将在后面更详细地介绍它们,但现在知道它们存在、可以运行逻辑并且作为命令运行就足够了。

别名

别名只是一个快捷方式。输入一组特定的字符,shell 就会用别名定义的值替换它们。

一些常用命令实际上已经是别名了 - 例如,在我的 `zsh` shell 中,`ls` 命令就是一个别名

% type -a ls
ls is an alias for ls -G
ls is /bin/ls

我确保当我使用 `ls` 命令时,shell 总是将其展开为 `ls -G`,这会对输出进行着色。

我们可以快速定义别名以节省按键次数。例如

$ alias k='kubectl'

从现在开始,我可以使用 `k` 别名作为 `kubectl` 命令的简写。

别名比函数要不那么复杂。将它们视为节省按键次数的工具,仅此而已,你就不会出错。

那么,有什么意义呢?

所以,我们现在应该对各种 shell 命令有了更深入的理解。并非所有命令都是可执行文件,并非所有我们认为是可执行文件的命令都一定是,并且有些命令可能更复杂。

作为 shell 用户,需要记住的关键点是

  1. 可执行文件是“安全”的 - 它们是你的系统可以使用的程序;你的 shell 只是调用它们。
  2. 内置命令是非常 shell 特定的,通常控制 shell 本身
  3. 函数是编写逻辑的强大方式,但通常是 shell 特定的。
  4. 别名是为了方便操作员使用而设计的,但仅限于交互式 shell 的上下文。

要找出命令是如何实现的,只需使用 `type -a` 命令

$ type -a cat
cat is /bin/cat

你不需要知道的更多

好的,对于那些喜欢自虐的少数人,你可能会想知道你可能看到的其他所有可以告诉你程序和命令的命令和实用程序

  • what
  • whatis
  • which
  • whence
  • 其中
  • whereis
  • command
  • type

很多这些都是遗留的,应该避免使用,但为了完整起见,我们将一一介绍。

what

`what` 读取程序中嵌入的特殊元数据,通常用于识别构建它的源代码版本

$ what /bin/ls
/bin/ls
         Copyright (c) 1989, 1993, 1994
        PROGRAM:ls  PROJECT:file_cmds-272.220.1

在你日常工作中几乎没有需要使用它的情况,但如果你本打算输入 `whatis`,你可能会遇到它。

whatis

`whatis` 在本地帮助数据库中搜索文本。这对于查找 man pages[2]很有用

$ whatis bash
bash(1)                  - GNU Bourne-Again SHell
bashbug(1)               - report a bug in bash

但我无法想象大多数用户会经常使用它。

which

`which` 将搜索你的 `$PATH` 以查看是否可以找到可执行文件。使用 `-a` 标志,它将显示所有结果。

$ which -a vi
/usr/local/bin/vi
/usr/bin/vi

`which` 源自 `csh`。它仍然存在于许多系统上是为了兼容性,但由于可能存在的奇怪行为,一般应避免使用[4]。

whence

`whence` 被添加到 Korn shell。除非你在使用 `ksh` 的系统上,否则你不太可能使用它。`zsh` 也有这个命令,但应该避免使用,并认为它不是标准的。

% whence brew
/usr/local/bin/brew

其中

这是一个 shell 内置命令,可以提供关于命令的信息,类似于 `type`

% where ls
ls: aliased to ls -G
/bin/ls

但是,`type` 应该更受欢迎,因为它更标准。

whereis

`whereis` 在某些系统上可用,并且通常与 `which` 类似,在路径中搜索可执行文件

% whereis ls
/bin/ls

同样,为了兼容性,应优先使用 `type`。

command

`command` 定义在 POSIX 标准中,因此在大多数现代系统上都应该存在。不带参数时,它只是执行一个命令。使用 `-v` 参数,你会得到一个相当容易通过机器读取或处理的响应;使用 `-V` 参数,你会得到一个更容易人类阅读的响应

% command -v ls
alias ls='ls -G'
% command -V ls
ls is an alias for ls -G

`command` 在脚本中可能很有用,我们将在后面的章节中看到。

type

`type` 是 Unix 标准的一部分,并且存在于大多数现代系统中。正如我们已经看到的,它将识别命令的类型以及可执行文件的位置

% type -a ls
ls is an alias for ls -G
ls is /bin/ls

此命令也可用于仅搜索路径

% type -p ls
ls is /bin/ls

摘要

总而言之,避免任何以“w”开头的命令!这些是遗留命令,通常只在处理旧 Unix 机器时需要。应该改用 `type` 或 `command`。

脚注

  1. 我们将在后面的章节中介绍权限和模式。 ↩︎

  2. 为什么是这些名称和位置?这是一个很长的故事。如果你有兴趣,最好的起点是 文件系统层次结构标准↩︎

  3. `chmod` 更改文件的模式;`+x` 意味着“添加可执行位”。这告诉操作系统文件可以被执行。 ↩︎

  4. Stack Exchange:为什么不用“which”?那该用什么呢? ↩︎

© . All rights reserved.