系统管理员 Bash 指南

使 Bash shell 更好地为您工作的技巧和窍门。
254 位读者喜欢这个。
GitHub launches Open Source Friday 

Opensource.com

每种行业都有一种工具,该行业的大师最常使用它。 对于许多系统管理员来说,该工具是他们的shell。 在大多数 Linux 和其他类 Unix 系统中,默认 shell 是 Bash。

Bash 是一个相当古老的程序——它起源于 20 世纪 80 年代后期——但它建立在更古老的 shell 之上,例如 C shell (csh),它至少比 Bash 早 10 年。 因为 shell 的概念如此古老,所以那里有大量神秘的知识等待被吸收,以使任何系统管理员的生活更轻松。

让我们来看看一些基础知识。

谁曾在某个时候无意中以 root 身份运行命令并导致某种问题? 举手

我很确定我们很多人都曾经是那样的人。 非常痛苦。 这里有一些非常简单的技巧可以防止您第二次撞到石头。

使用别名

首先,为像 mvrm 这样的命令设置别名,使其指向 mv -Irm -I。 这将确保运行 rm -f /boot 至少会要求您确认。 在 Red Hat Enterprise Linux 中,如果您使用 root 帐户,则默认情况下会设置这些别名。

如果您也想为您的普通用户帐户设置这些别名,只需将这两行放入您主目录中名为 .bashrc 的文件中(这些别名也适用于 sudo)

alias mv='mv -i'
alias rm='rm -i'

让您的 root 提示符突出显示

您可以做的另一件事来防止意外是确保您知道何时正在使用 root 帐户。 我通常通过使 root 提示符与我用于日常工作的普通提示符非常不同来做到这一点。

如果您将以下内容放入 root 主目录中的 .bashrc 文件中,您将拥有一个红底黑字的 root 提示符,这清楚地表明您(或任何其他人)都应该小心谨慎。

export PS1="\[$(tput bold)$(tput setab 0)$(tput setaf 1)\]\u@\h:\w # \[$(tput sgr0)\]"

事实上,您应该尽可能避免以 root 身份登录,而是通过 sudo 运行您的大部分系统管理命令,但这又是另一个故事了。

实施了一些小的技巧来帮助防止使用 root 帐户的“意外副作用”后,让我们看看 Bash 可以帮助您在日常工作中做的一些好事情。

控制您的历史记录

您可能知道,当您在 Bash 中按向上箭头键时,您可以看到并重用所有(嗯,很多)以前的命令。 这是因为这些命令已保存到您主目录中名为 .bash_history 的文件中。 该历史记录文件带有一堆设置和命令,这些设置和命令可能非常有用。

首先,您可以通过键入 history 来查看您的整个最近命令历史记录,或者您可以通过键入 history 30 将其限制为最近 30 个命令。 但这非常普通。 您可以更好地控制 Bash 保存的内容以及保存方式。

例如,如果您将以下内容添加到您的 .bashrc 中,则任何以空格开头的命令都不会保存到历史记录列表中

HISTCONTROL=ignorespace

如果您需要在纯文本中将密码传递给命令,这可能很有用。 (是的,这很糟糕,但仍然会发生。)

如果您不希望频繁执行的命令出现在您的历史记录中,请使用

HISTCONTROL=ignorespace:erasedups

这样,每次您使用命令时,其所有以前的出现都将从历史记录文件中删除,并且只有最后一次调用会保存到您的历史记录列表中。

我特别喜欢的历史记录设置是 HISTTIMEFORMAT 设置。 这将在您的历史记录文件中为所有条目添加时间戳。 例如,我使用

HISTTIMEFORMAT="%F %T  "

当我键入 history 5 时,我会得到漂亮、完整的信息,如下所示

1009  2018-06-11 22:34:38  cat /etc/hosts
1010  2018-06-11 22:34:40  echo $foo
1011  2018-06-11 22:34:42  echo $bar
1012  2018-06-11 22:34:44  ssh myhost
1013  2018-06-11 22:34:55  vim .bashrc

这使得浏览我的命令历史记录并找到我两天前用于设置到我的家庭实验室的 SSH 隧道的命令(我一次又一次地忘记它……)变得容易得多。

最佳 Bash 实践

我将用我的最佳(或至少是好的;我不声称是全知全能的)Bash 脚本编写实践的 11 条列表来总结这一点。

  1. Bash 脚本可能会变得复杂,而注释很便宜。 如果您想知道是否要添加注释,请添加注释。 如果您在周末后回来并且必须花费时间弄清楚您上周五试图做什么,那么您忘记添加注释了。

  1. 将所有变量名称都用花括号括起来,例如 ${myvariable}。 将此作为习惯可以使 ${variable}_suffix 等成为可能,并提高整个脚本的一致性。
  1. 在评估表达式时不要使用反引号; 请改用 $() 语法。 所以使用
    for  file in $(ls); do

    而不是

    for  file in `ls`; do

    前一种选择是可嵌套的,更易于阅读,并且让一般的系统管理员群体感到高兴。 不要使用反引号。

  1. 一致性是好的。 选择一种做事风格并在整个脚本中坚持使用它。 显然,如果人们选择 $() 语法而不是反引号并将他们的变量用花括号括起来,我会更喜欢。 如果人们使用两个或四个空格(而不是制表符)来缩进,我会更喜欢,但即使您选择做错,也要始终如一地做错。
  1. 为 Bash 脚本使用正确的 shebang。 当我编写 Bash 脚本的目的只是为了用 Bash 执行它们时,我最常使用 #!/usr/bin/bash 作为我的 shebang。 不要使用 #!/bin/sh#!/usr/bin/sh。 您的脚本将执行,但它将在兼容模式下运行——可能会产生许多意想不到的副作用。 (当然,除非您想要兼容模式。)
  1. 在比较字符串时,最好在 if 语句中引用您的变量,因为如果您的变量为空,Bash 将为如下行抛出错误
    if [ ${myvar} == "foo" ]; then
      echo "bar"
    fi

    并且对于如下行将评估为 false

    if [ "${myvar}" == "foo" ]; then
      echo "bar"
    fi  

    此外,如果您不确定变量的内容(例如,当您解析用户输入时),请引用您的变量以防止解释某些特殊字符,并确保将变量视为单个单词,即使它包含空格。

  1. 我想这是一种品味问题,但我更喜欢使用双等号 (==),即使在 Bash 中比较字符串时也是如此。 这是一种一致性问题,即使——仅对于字符串比较——单个等号也可以工作,但我的脑海中立即闪现“单等号是赋值运算符!”
  1. 使用正确的退出代码。 确保如果您的脚本未能做某事,您会向用户显示一条书面失败消息(最好提供解决问题的方法)并发送一个非零退出代码
    # we have failed
    echo "Process has failed to complete, you need to manually restart the whatchamacallit"
    exit 1

    这使得从另一个脚本以编程方式调用您的脚本并验证其成功完成变得更容易。

  1. 使用 Bash 的内置机制为您的变量提供合理的默认值,或者在您期望定义的变量未定义时抛出错误
    # this sets the value of $myvar to redhat, and prints 'redhat'
    echo ${myvar:=redhat}
    # this throws an error reading 'The variable myvar is undefined, dear reader' if $myvar is undefined
    ${myvar:?The variable myvar is undefined, dear reader}
  1. 特别是如果您正在编写一个大型脚本,特别是如果您与其他人一起处理该大型脚本,请考虑在函数内部定义变量时使用 local 关键字。 local 关键字将创建一个局部变量,即仅在该函数内可见的变量。 这限制了变量冲突的可能性。
  1. 每个系统管理员有时都必须这样做:在控制台上调试某些东西,无论是数据中心中的真实控制台还是通过虚拟化平台的虚拟控制台。 如果您必须以这种方式调试脚本,您会感谢自己记住这一点:不要使脚本中的行太长!



    在许多系统中,控制台的默认宽度仍然是 80 个字符。 如果您需要在控制台上调试脚本,并且该脚本的行非常长,那么您会成为一只悲伤的熊猫。 此外,具有较短行的脚本——默认值仍然是 80 个字符——在普通编辑器中也更容易阅读和理解!

我真的很喜欢 Bash。 我可以花几个小时写关于它的文章,或者与同道中人交流一些不错的技巧。 请务必在评论中留下您最喜欢的技巧!

User profile image.
嗨! 我是 Maxim,Red Hat Benelux 团队的解决方案架构师和布道者。 Red Hat 以各种可能的方式辐射开源,这使其成为像我这样的开源爱好者工作的完美公司。

16 条评论

很棒的文章,Maxim! 我使用 Linux 已经很长时间了,我从您的文章中学到了一些新东西。 我特别喜欢用法 ${variable} 和 "for file in $(ls)"。 我不知道为什么我以前不知道这些。

我不同意您关于使用 sudo 的说法。 我更喜欢以 root 身份登录,因为使用 sudo 意味着更多的打字,效率较低。

一篇很棒的文章。

谢谢 David! 很高兴你喜欢它!

至于 sudo:虽然我同意直接以 root 身份登录稍微容易一些,但从安全角度来看,它带走了一些审核用户以及可能将个人行为与事件关联起来的重要机会。

使用 sudo 给某人临时特权访问机器,或者只给某人一些特权而不是全部特权也容易得多;)

事实上,即使在我自己的机器上我也使用 sudo:我需要花费时间输入密码也是我需要重新思考我刚刚输入的命令的时间;)

至于打字更少:我一直在使用 Ansible 来实现这一目标。 最近也有一篇关于使用 Ansible 的文章:https://open-source.net.cn/article/18/7/sysadmin-tasks-ansible

祝您系统管理愉快!

回复 作者 dboth

我一直同意你的观点,直到你推荐使用空格代替制表符,你这个肮脏的异教徒。

说真的,这里有一些很好的技巧。 我实际上最近开始自己使用其中的一些(参数替换非常棒)。 Bash 显然不再是时髦的东西了,但它非常可靠。 很高兴看到有人喜欢它。

您最好不要乱动任何众所周知的命令,如 mv 或 rm,因为有一天您会在一个没有这些别名的系统上,那时是凌晨 3 点,您会得到一个非常糟糕的惊喜。 可能是以 root 身份。

更可行的方法:使用脚本(例如,“del”)通过将文件移动到同一文件系统上的回收站目录来删除文件,这样您就不必整天等待删除一些非常大的文件。 这至少让您有机会改变主意。

我恭敬地不同意。 它不是依赖于该别名的存在,而是在您不小心或以错误的用户身份键入错误命令时的安全网。

但是,如果一致性是需要解决的点,那么 Ansible 在确保所有服务器上的所有别名都相同方面做得很好;)

回复 作者 Karl Vogel (未验证)

制表符很棒!

我建议通过 shellcheck 运行所有脚本 (https://github.com/koalaman/shellcheck) 适用于大多数 linux 发行版。 当您不使用上述最佳实践时,它会给您警告。 它也很容易与您的 CI 工作流程集成(例如通过 jenkins checkstyle 插件)

此外,如果您更喜欢最新的 shellcheck,您可以从其 github 存储库下载 shellcheck 的预编译二进制文件。

顺便说一句,shellcheck 会抱怨您不应该使用 "for file in $(ls)",而应该使用 "for file in *" :)

回复 作者 Johan Ekblad (未验证)

这实际上是一个非常好的建议! 我一定会研究一下,谢谢!

回复 作者 Johan Ekblad (未验证)

-f 覆盖 rm 命令中的 -i。 别名将阻止 "rm /" 但不会阻止 "rm -f /"

否则,这里有一些有用的信息

如果您在 vim 中并且正在处理文件但忘记了 sudo,请使用以下命令,而不是退出并重做您的工作
:w !sudo tee %

嗨 Maxim,

好文章! 我之前从其他人那里听说过反引号已被弃用。 您能详细说明一下背后的原因吗?

我曾经是这些反引号的忠实粉丝,但自从听说它已被弃用以来,我一直在适应。

我不知道它们是否已被弃用,但您无法嵌套它们这一事实足以让我不使用它们。 我也更喜欢 $(foo) 而不是 `foo` 的语法:很清楚 $(foo) 语句的开始和结束位置,即使您执行 $(foo $(bar)) 也是如此。 使用 `foo `bar`` 就困难得多。

回复 作者 Ted Kraan (未验证)

请允许我进行更正/建议。 条件测试最好在 bash 中用 [[ ]] 表示,如

if [[ ${myvar} == "foo" ]]; then
echo "bar"
fi

如果 myvar 未定义或为空,此代码段不会抛出错误。 更好的是,它不会对扩展 ${myvar} 进行单词拆分。

使用 [ ] 是 sh 的测试方式,而不是 bash 的方式。

嗨 Maxim,
感谢您的这篇有趣的文章。
- 对于 Shebang,我个人更喜欢使用 "#!/usr/bin/env bash"。 它更适合移植性,因为您不能确定 bash 可执行文件始终位于 /usr/bin 下。
- 我个人专门使用 $() 而不是反引号,并且喜欢将花括号与变量一起使用。 它干净且安全。
- 正如 Paulo Marcelo Coelho Aragao 所提到的,我更喜欢使用双中括号 [[ ]] 进行条件测试。 (参见 https://tldp.cn/LDP/abs/html/testconstructs.html#DBLBRACKETS
- 为了清晰起见,在脚本中,始终使用一个 usage 函数,您可以在测试脚本参数时调用它。 例子
usage() {
echo "用法: $0 主机名"
echo " 主机名:要检查的主机名称"
exit 1
}

[[ -z ${hostName} ]] && usage

- 拥有您自己的脚本来帮助您轻松快速地完成事情也是一个好主意。 我通常将它们放在一个目录中,我将该目录附加到 PATH 环境变量。

嗨 Mossaab,

感谢您的评论! (并为我自己的迟复表示歉意... 度假发生了 :))

我个人不太喜欢 env 语句,部分原因是我的脚本都针对 Linux,因此像 BSD 上那样的 /usr/local/bin/bash 中的 Bash 风险不大。 第二个更重要的原因是,env 语句可能会使用与系统 Bash 不同的 Bash,从而可能产生意想不到的结果。 (当然,这在 Bash 中比在 Python 中问题要少。)

双中括号是一个合理的观点,我将在本文或其他文章的未来版本中考虑这一点。

usage() 函数也很不错,谢谢!

回复 作者 Mossaab Stiri

Creative Commons License本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.