标签 脚本 下的文章

来自我的邮箱:

我写了一个 hello world 小脚本。我如何能调试运行在 Linux 或者类 UNIX 的系统上的 bash shell 脚本呢?

这是 Linux / Unix 系统管理员或新用户最常问的问题。shell 脚本调试可能是一项繁琐的工作(不容易阅读)。调试 shell 脚本有多种方法。

您需要传递 -x-v 参数,以在 bash shell 中浏览每行代码。

如何在 Linux 或者 UNIX 下调试 Bash Shell 脚本

让我们看看如何使用各种方法调试 Linux 和 UNIX 上运行的脚本。

-x 选项来调试脚本

-x 选项来运行脚本:

$ bash -x script-name
$ bash -x domains.sh

使用 set 内置命令

bash shell 提供调试选项,可以打开或关闭使用 set 命令

  • set -x : 显示命令及其执行时的参数。
  • set -v : 显示 shell 输入行作为它们读取的

可以在 shell 脚本本身中使用上面的两个命令:

#!/bin/bash
clear

# turn on debug mode
set -x
for f in *
do
   file $f
done
# turn OFF debug mode
set +x
ls
# more commands

你可以代替 标准释伴 行:

#!/bin/bash

用以下代码(用于调试):

#!/bin/bash -xv

使用智能调试功能

首先添加一个叫做 _DEBUG 的特殊变量。当你需要调试脚本的时候,设置 _DEBUGon

_DEBUG="on"

在脚本的开头放置以下函数:

function DEBUG()
{
 [ "$_DEBUG" == "on" ] &&  $@
}

现在,只要你需要调试,只需使用 DEBUG 函数如下:

DEBUG echo "File is $filename"

或者:

DEBUG set -x
Cmd1
Cmd2
DEBUG set +x

当调试完(在移动你的脚本到生产环境之前)设置 _DEBUGoff。不需要删除调试行。

_DEBUG="off" # 设置为非 'on' 的任何字符

示例脚本:

#!/bin/bash
_DEBUG="on"
function DEBUG()
{
 [ "$_DEBUG" == "on" ] &&  $@
}

DEBUG echo 'Reading files'
for i in *
do
  grep 'something' $i > /dev/null
  [ $? -eq 0 ] && echo "Found in $i file"
done
DEBUG set -x
a=2
b=3
c=$(( $a + $b ))
DEBUG set +x
echo "$a + $b = $c"

保存并关闭文件。运行脚本如下:

$ ./script.sh

输出:

Reading files
Found in xyz.txt file
+ a=2
+ b=3
+ c=5
+ DEBUG set +x
+ '[' on == on ']'
+ set +x
2 + 3 = 5

现在设置 _DEBUGoff(你需要编辑该文件):

_DEBUG="off"

运行脚本:

$ ./script.sh

输出:

Found in xyz.txt file
2 + 3 = 5

以上是一个简单但非常有效的技术。还可以尝试使用 DEBUG 作为别名而不是函数。

调试 Bash Shell 的常见错误

Bash 或者 sh 或者 ksh 在屏幕上给出各种错误信息,在很多情况下,错误信息可能不提供详细的信息。

跳过在文件上应用执行权限

当你 编写你的第一个 hello world 脚本,您可能会得到一个错误,如下所示:

bash: ./hello.sh: Permission denied

设置权限使用 chmod 命令:

$ chmod +x hello.sh
$ ./hello.sh
$ bash hello.sh

文件结束时发生意外的错误

如果您收到文件结束意外错误消息,请打开脚本文件,并确保它有打开和关闭引号。在这个例子中,echo 语句有一个开头引号,但没有结束引号:

#!/bin/bash

...
....

echo 'Error: File not found
                           ^^^^^^^
                           missing quote

还要确保你检查缺少的括号和大括号 {}

#!/bin/bash
.....
[ ! -d $DIRNAME ] && { echo "Error: Chroot dir not found"; exit 1;
                                                                    ^^^^^^^^^^^^^
                                                                    missing brace }
...

丢失像 fi,esac,;; 等关键字。

如果你缺少了结尾的关键字,如 fi;; 你会得到一个错误,如 “XXX 意外”。因此,确保所有嵌套的 ifcase 语句以适当的关键字结束。有关语法要求的页面。在本例中,缺少 fi

#!/bin/bash
echo "Starting..."
....
if [ $1 -eq 10 ]
then
   if [ $2 -eq 100 ]
   then
      echo "Do something"
fi

for f in $files
do
  echo $f
done

# 注意 fi 丢失了

在 Windows 或 UNIX 框中移动或编辑 shell 脚本

不要在 Linux 上创建脚本并移动到 Windows。另一个问题是编辑 Windows 10上的 shell 脚本并将其移动到 UNIX 服务器上。这将由于换行符不同而导致命令没有发现的错误。你可以使用下列命令 将 DOS 换行转换为 CR-LF 的Unix/Linux 格式

dos2unix my-script.sh

技巧

技巧 1 - 发送调试信息输出到标准错误

[标准错误] 是默认错误输出设备,用于写所有系统错误信息。因此,将消息发送到默认的错误设备是个好主意:

# 写错误到标准输出
echo "Error: $1 file not found"
#
# 写错误到标准错误(注意 1>&2 在 echo 命令末尾)
#
echo "Error: $1 file not found" 1>&2

技巧 2 - 在使用 vim 文本编辑器时,打开语法高亮

大多数现代文本编辑器允许设置语法高亮选项。这对于检测语法和防止常见错误如打开或关闭引号非常有用。你可以在不同的颜色中看到。这个特性简化了 shell 脚本结构中的编写,语法错误在视觉上截然不同。高亮不影响文本本身的意义,它只为你提示而已。在这个例子中,我的脚本使用了 vim 语法高亮:

!如何调试 Bash Shell 脚本,在 Linux 或者 UNIX 使用 Vim 语法高亮特性]7

技巧 3 - 使用 shellcheck 检查脚本

shellcheck 是一个用于静态分析 shell 脚本的工具。可以使用它来查找 shell 脚本中的错误。这是用 Haskell 编写的。您可以使用这个工具找到警告和建议。你可以看看如何在 Linux 或 类UNIX 系统上安装和使用 shellcheck 来改善你的 shell 脚本,避免错误和高效。

作者:Vivek Gite

作者是 nixCraft 创造者,一个经验丰富的系统管理员和一个练习 Linux 操作系统/ UNIX shell 脚本的教练。他曾与全球客户和各种行业,包括 IT,教育,国防和空间研究,以及非营利部门。关注他的 推特脸谱网谷歌+


via: https://www.cyberciti.biz/tips/debugging-shell-script.html

作者:Vivek Gite 译者:zjon 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

正则表达式 Regular expressions (简写为 regex 或者 regexp)基本上是定义一种搜索模式的字符串,可以被用来执行“搜索”或者“搜索并替换”操作,也可以被用来验证像密码策略等条件。

正则表达式是一个我们可利用的非常强大的工具,并且使用正则表达式的优点是它能在几乎所有计算机语言中被使用。所以如果你使用 Bash 脚本或者创建一个 python 程序时,我们可以使用正则表达式,或者也可以写一个单行搜索查询。

在这篇教程中,我们将会学习一些正则表达式的基本概念,并且学习如何在 Bash 中通过 grep 使用它们,但是如果你希望在其他语言如 python 或者 C 中使用它们,你只能使用正则表达式部分。那么让我们通过正则表达式的一个例子开始吧,

正则表达式看起来像 /t[aeiou]l/ 这个样子。

但这是什么意思呢?它意味着所提到的正则表达式将寻找一个词,它以 t 开始,在中间包含字母 a e i o u 中任意一个,并且字母 l 最为最后一个字符。它可以是 teltal 或者 til,可以匹配一个单独的词或者其它单词像 tiltbrutal 或者 telephone 的一部分。

grep 使用正则表达式的语法是 $ grep "regex_search_term" file_location

如果不理解,不要担心,这只是一个例子,来展示可以利用正则表达式获取什么,相信我,这是最简单的例子。我们可以从正则表达式中获取更多。现在我们将从正则表达式基础的开始。

基础的正则表示式

现在我们开始学习一些被称为 元字符 MetaCharacters 的特殊字符。它们可以帮助我们创建更复杂的正则表达式搜索项。下面提到的是基本元字符的列表,

  • . 点将匹配任意字符
  • [ ] 将匹配一个字符范围
  • [^ ] 将匹配除了括号中提到的那个之外的所有字符
  • * 将匹配零个或多个前面的项
  • + 将匹配一个或多个前面的项
  • ? 将匹配零个或一个前面的项
  • {n} 将匹配 n 次前面的项
  • {n,} 将匹配 n 次或更多前面的项
  • {n,m} 将匹配在 n 和 m 次之间的项
  • {,m} 将匹配少于或等于 m 次的项
  • \ 是一个转义字符,当我们需要在我们的搜索中包含一个元字符时使用

现在我们将用例子讨论所有这些元字符。

. (点)

它用于匹配出现在我们搜索项中的任意字符。举个例子,我们可以使用点如:

$ grep "d.g" file1

这个正则表达式意味着我们在名为 ‘file1’ 的文件中查找的词以 d 开始,以 g结尾,中间可以有 1 个字符的字符串。同样,我们可以使用任意数量的点作为我们的搜索模式,如 T......h,这个查询项将查找一个词,以 T 开始,以 h 结尾,并且中间可以有任意 6 个字符。

[ ]

方括号用于定义字符范围。例如,我们需要搜索一些特别的单词而不是匹配任何字符,

$ grep "N[oen]n" file2

这里,我们正寻找一个单词,以 N开头,以 n 结尾,并且中间只能有 oe 或者 n 中的一个。 在方括号中我们可以提到单个到任意数量的字符。

我们在方括号中也可以定义像 a-e或者 1-18 作为匹配字符的列表。

[^ ]

这就像正则表达式的 not 操作。当使用 [^ ] 时,它意味着我们的搜索将包括除了方括号内提到的所有字符。例如,

$ grep "St[^1-9]d" file3

这意味着我们可以拥有所有这样的单词,它们以 St 开始,以字母 d 结尾,并且不得包含从 19 的任何数字。

到现在为止,我们只使用了仅需要在中间查找单个字符的正则表达式的例子,但是如果我们需要更多字符该怎么办呢。假设我们需要找到以一个字符开头和结尾的所有单词,并且在中间可以有任意数量的字符。这就是我们使用乘数元字符如 + *? 的地方。

{n}{n,m}{n,} 或者 {,m} 也是可以在我们的正则表达式项中使用的其他乘数元字符。

* (星号)

以下示例匹配字母 k 的任意出现次数,包括一次没有:

$ grep "lak*" file4

它意味着我们可以匹配到 lakela 或者 lakkkk

+

以下模式要求字符串中的字母 k 至少被匹配到一次:

$ grep "lak+" file5

这里 k 在我们的搜索中至少需要发生一次,所以我们的结果可以为 lake 或者 lakkkk,但不能是 la

?

在以下模式匹配中

$ grep "ba?b" file6

匹配字符串 bbbab,使用 ? 乘数,我们可以有一个或零个字符的出现。

非常重要的提示

当使用乘数时这是非常重要的,假设我们有一个正则表达式

$ grep "S.*l" file7

我们得到的结果是 smallsilly,并且我们也得到了 Shane is a little to play ball。但是为什么我们得到了 Shane is a little to play ball?我们只是在搜索中寻找单词,为什么我们得到了整个句子作为我们的输出。

这是因为它满足我们的搜索标准,它以字母 s 开头,中间有任意数量的字符并以字母 l 结尾。那么,我们可以做些什么来纠正我们的正则表达式来只是得到单词而不是整个句子作为我们的输出。

我们在正则表达式中需要增加 ? 元字符,

$ grep "S.*?l" file7

这将会纠正我们正则表达式的行为。

\

\ 是当我们需要包含一个元字符或者对正则表达式有特殊含义的字符的时候来使用。例如,我们需要找到所有以点结尾的单词,所以我们可以使用:

$ grep "S.*\\." file8

这将会查找和匹配所有以一个点字符结尾的词。

通过这篇基本正则表达式教程,我们现在有一些关于正则表达式如何工作的基本概念。在我们的下一篇教程中,我们将学习一些高级的正则表达式的概念。同时尽可能多地练习,创建正则表达式并试着尽可能多的在你的工作中加入它们。如果有任何疑问或问题,您可以在下面的评论区留言。


via: http://linuxtechlab.com/bash-scripting-learn-use-regex-basics/

作者:SHUSAIN 译者:kimii 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

通过一些简单的 Google 搜索,即使是编程入门者也可以尝试编写代码将以往枯燥和冗长的任务自动化。

 title=

我前几天写了一个脚本。对于一些人来说,这句话听起来没什么了不起的。而对于另一些人来说,这句话意义重大。要知道,我不是一个程序员,而是一个作家。

我需要解决什么?

我的问题相当简单:我需要将工程文件进行分类。这些文件可以从一个网站 URL 以 .zip 的格式下载。当我正手工将它们拷贝到我的电脑桌面,并移动到一个已按照我文件分类的需要进行了结构化的目录时,一位作家同事给我提了建议:“不就是写个脚本的事吗?”

我心想:“就写个脚本?”——说得好像这是世界上最容易做的事情一样。

Google 是如何解救我的?

同事的问题促使我思考,并且经过思考后,我进行了 Google 搜索。

Linux 上使用的是什么脚本编程语言?

这是我第一个 Google 搜索的准则。也许很多人心里会想:“她太笨了!”是的,我很笨。不过,这的确使我走上了一条解决问题的道路。最常见的搜索结果是 Bash 。嗯,我听说过 Bash 。呃,我要分类的文件中有一个里面就有 Bash,那无处不在的 #!/bin/bash 。我重新看了下那个文件,我知道它的用途,因为我需要将它分类。

这引导我进行了下一个 Google 搜索。

如何从一个 URL 下载 zip 文件?

那确实是我的基本任务。我有一个带有 .zip 文件的 URL ,它包含有所有我需要分类的文件,所以我寻求万能的 Google 的帮助。搜索到的精华内容和其它一些结果引导我使用 Curl 。但最重要的是:我不仅找到了 Curl ,其中一条置顶的搜索结果还展示了一个使用 Curl 去下载并解压 .zip 文件的 Bash 脚本。这超出了我本来想寻求的答案,但那也使我意识到在 Google 搜索具体的请求可以得到我写这个脚本需要的信息。所以,在这个收获的推动下,我写了最简单的脚本:

#!/bin/sh

curl http://rather.long.url | tar -xz -C my_directory --strip-components=1

我迫不及待地运行看看。但我发现一个问题: URL 是会变的,根据我要访问的文件的分组不同而不同。我有新的问题需要解决,这使我进行了下一轮搜索。

参数如何传递给 Bash 脚本?

我需要以不同的 URL 和不同的最终目录来运行此脚本。 Google 向我展示了如何使用 $1$2 等等来替换我在命令行中运行脚本时输入的内容。比如:

bash myscript.sh http://rather.long.url my_directory

这就好多了。一切如我所愿,灵活,实用。最重要的是我只要输入一条简短的命令就可以节省 30 分钟无聊的复制、粘贴工作。这个早上的时间花得值得。

然后我发现还有一个问题:我很健忘,并且我知道我几个月才运行一次这个脚本。这留给我两个疑问:

  • 我要如何记得运行脚本时输入什么(URL 先,还是目录先)?
  • 如果我被货车撞了,其它作家如何知道该怎样运行我的脚本?

我需要一个使用说明 —— 如果我使用不正确,则脚本会提示。比如:

usage: bash yaml-fetch.sh <'snapshot_url'> <directory>

否则,则直接运行脚本。我的下一个搜索是:

如何在 Bash 脚本里使用 “if/then/else”?

幸运的是,我已经知道编程中 if/then/else 的存在。我只要找出如何使用它的方法。在这个过程中,我也学到了如何在 Bash 脚本里使用 echo 打印。我的最终成果如下:

#!/bin/sh

URL=$1
DIRECTORY=$2

if [ $# -eq 0 ];
 then
 echo "usage: bash yaml-fetch.sh <'snapshot_url'> <directory>".
 else

# 如果目录不存在则创建它
 echo 'create directory'

 mkdir $DIRECTORY

 # 下载并解压 yaml 文件
 echo 'fetch and untar the yaml files'

 curl $URL | tar -xz -C $DIRECTORY --strip-components=1
fi

Google 和脚本编程如何颠覆我的世界?

好吧,这稍微有点夸大,不过现在是 21 世纪,学习新东西(特别是稍微简单的东西)比以前简单多了。我所学到的(除了如何写一个简短的、自动分类的 Bash 脚本之外)是如果我有疑问,那么有很大可能性是其它人在之前也有过相同的疑问。当我困惑时,我可以问下一个问题,再下一个问题。最后,我不仅拥有了脚本,还拥有了可以一直拥有并可以简化其它任务的新技能,这是我之前所没有的。

别止步于第一个脚本(或者编程的第一步)。这是一个技能,和其它的技能并无不同,有大量的信息可以在这一路上帮助你。你无需阅读大量的书或参加一个月的课程。你可以像婴儿学步那样简单地开始写脚本,然后掌握技能并建立自信。人们总有写成千上万行代码的需求,并对它进行分支、合并、修复错误。但是,通过简单的脚本或其它方式来自动化、简单化任务的需求也一样强烈。这样的一个小脚本和小小的自信就能够让你启程脚本编程之路。

(题图: opensource.com)


作者简介:

Sandra McCann 是一位 Linux 和开源技术的倡导者。她是一位软件开发者、学习资源内容架构师、内容创作者。桑德拉目前是位于韦斯特福德马萨诸塞州的红帽公司的内容创作者,专注于 OpenStack 和 NFV 技术。


via: https://opensource.com/article/17/5/how-i-learned-bash-scripting

作者:Sandra McCann 译者:xllc 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Shell 脚本编程 是你在 Linux 下学习或练习编程的最简单的方式。尤其对 系统管理员要处理着自动化任务,且要开发新的简单的实用程序或工具等(这里只是仅举几例)更是必备技能。

本文中,我们将分享 10 个写出高效可靠的 bash 脚本的实用技巧,它们包括:

1、 脚本中多写注释

这是不仅可应用于 shell 脚本程序中,也可用在其他所有类型的编程中的一种推荐做法。在脚本中作注释能帮你或别人翻阅你的脚本时了解脚本的不同部分所做的工作。

对于刚入门的人来说,注释用 # 号来定义。

# TecMint 是浏览各类 Linux 文章的最佳站点

2、 当运行失败时使脚本退出

有时即使某些命令运行失败,bash 可能继续去执行脚本,这样就影响到脚本的其余部分(会最终导致逻辑错误)。用下面的行的方式在遇到命令失败时来退出脚本执行:

# 如果命令运行失败让脚本退出执行
set -o errexit 
# 或
set -e

3、 当 Bash 用未声明变量时使脚本退出

Bash 也可能会使用能导致起逻辑错误的未声明的变量。因此用下面行的方式去通知 bash 当它尝试去用一个未声明变量时就退出脚本执行:

# 若有用未设置的变量即让脚本退出执行
set -o nounset
# 或
set -u

4、 使用双引号来引用变量

当引用时(使用一个变量的值)用双引号有助于防止由于空格导致单词分割开和由于识别和扩展了通配符而导致的不必要匹配。

看看下面的例子:

#!/bin/bash
# 若命令失败让脚本退出
set -o errexit 
# 若未设置的变量被使用让脚本退出
set -o nounset
echo "Names without double quotes" 
echo

names="Tecmint FOSSMint Linusay"

for name in $names; do
  echo "$name"
done

echo
echo "Names with double quotes" 
echo

for name in "$names"; do
  echo "$name"
done

exit 0

保存文件并退出,接着如下运行一下:

$ ./names.sh

Use Double Quotes in Scripts

在脚本中用双引号

5、 在脚本中使用函数

除了非常小的脚本(只有几行代码),总是记得用函数来使代码模块化且使得脚本更可读和可重用。

写函数的语法如下所示:

function check_root(){
  command1; 
  command2;
}
# 或
check_root(){
  command1; 
  command2;
}

写成单行代码时,每个命令后要用终止符号:

check_root(){ command1; command2; }

6、 字符串比较时用 = 而不是 ==

注意 === 的同义词,因此仅用个单 = 来做字符串比较,例如:

value1=”tecmint.com”
value2=”fossmint.com”
if [ "$value1" = "$value2" ]

7、 用 $(command) 而不是老旧的 command 来做代换

命令代换 是用这个命令的输出结果取代命令本身。用 $(command) 而不是引号 command 来做命令代换。

这种做法也是 shellcheck tool (可针对 shell 脚本显示警告和建议)所建议的。例如:

user=`echo “$UID”`
user=$(echo “$UID”)

8、 用 readonly 来声明静态变量

静态变量不会改变;它的值一旦在脚本中定义后不能被修改:

readonly passwd_file=”/etc/passwd”
readonly group_file=”/etc/group”

9、 环境变量用大写字母命名,而自定义变量用小写

所有的 bash 环境变量用大写字母去命名,因此用小写字母来命名你的自定义变量以避免变量名冲突:

# 定义自定义变量用小写,而环境变量用大写
nikto_file=”$HOME/Downloads/nikto-master/program/nikto.pl”
perl “$nikto_file” -h  “$1”

10、 总是对长脚本进行调试

如果你在写有数千行代码的 bash 脚本,排错可能变成噩梦。为了在脚本执行前易于修正一些错误,要进行一些调试。通过阅读下面给出的指南来掌握此技巧:

  1. 如何在 Linux 中启用 Shell 脚本调试模式
  2. 如何在 Shell 脚本中执行语法检查调试模式
  3. 如何在 Shell 脚本中跟踪调试命令的执行

本文到这就结束了,你是否有一些其他更好的 bash 脚本编程经验想要分享?若是的话,在下面评论框分享出来吧。


作者简介:

Aaron Kili 是一个 Linux 和 F.O.S.S(Free and Open-Source Software,自由及开放源代码软件)爱好者,未来的 Linux 系统管理员、Web 开发人员,目前是 TecMint 的内容创作者,他喜欢用电脑工作,且崇尚分享知识。


via: https://www.tecmint.com/useful-tips-for-writing-bash-scripts-in-linux/

作者:Aaron Kili 译者:ch-cn 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

昨天,微博上的朋友 @马甲与小号 告诉我发现了一个奇怪的东西,本着好奇心使人进步(害死猫?)的目的,我去观摩了一番,于是就有了今天这篇文章。

这是一个 bash shell 脚本,其中有若干可以整蛊(结仇)你的同事的小技巧——或者说恶作剧。看完之后,感觉不寒而栗,要是谁敢这样整我,我一定和他绝交!

警告,切勿在生产环境体验,一切后果脚本作者和本文作者均不不承担!

警告,切勿在生产环境体验,一切后果脚本作者和本文作者均不不承担!

警告,切勿在生产环境体验,一切后果脚本作者和本文作者均不不承担!

这个世界怎么了?

这个脚本主要由一些别名、函数、环境变量定义组织而成,执行该脚本后,你的 shell 环境就变成了一个光怪陆离的世界。

好了,那么我们来看看都发生了什么。

注:本脚本适用于 bash 环境,其它 shell 环境有些不支持。

可怕的默认编辑器

当系统调用默认编辑器来编辑比如 crontab 时,biu 的一下,文件没了!

export EDITOR=/bin/rm;

这是将 EDITOR 环境变量定义为 rm,而它原本应该是 viemacsnano 的,体会一下,是不是很酸爽?

学习课堂:

EDITOR 环境变量用于定于系统的默认编辑器,在一些系统内置功能里面,比如编辑 crontab 时,会根据该变量调用默认编辑器。

猥琐的制表符(tab)

当你想用制表符来个自动补全时,你会它非但不干,而且还删除了一个字母,不信邪的你使劲多砸了几个制表符,这下好了,更多的字符被删除了。

tset -Qe $'\t';

原来是将制表符定义为退格键了。

学习课堂:

tset 用于设置终端特征;-e 参数设置擦除字符,缺省为退格字符;-Q 表示不显示设置信息(静默)。

莫名退出

有时候,执行一个命令就会莫名其妙地退出 shell,只不过是命令有个非 0 的返回状态嘛,为什么会这样?

((RANDOM % 10)) || set -o errexit;

学习课堂:

set -o errexit 等价于 set -e,表示有任何错误(命令的返回状态非 0 )时即退出。

啥都看(cat)不了

当我想看(cat)一下文件时,它居然就当没听见,到底文件里面有啥啊?

alias cat=true;

原来是把 cat 定义成 true 命令的别名了, true 命令啥都不干,不管你给它什么参数和什么输入,它只是静静地返回一个 0 的状态码。

学习课堂

true 命令和 false 命令常用于 shell 脚本中。

到底是按什么排列的啊?

好吧,我想看看目录里面有啥文件,于是我输入了 ls,咦?这是什么顺序?我再次输入一遍,怎么回事,列出的文件和目录又是一种顺序,难道它的输出看心情吗?

function ls { command ls -$(opts="frStu"; echo ${opts:$((RANDOM % ${#opts})):1}) "$@"; }

原来它用一个函数重新定义了 ls,所以,真是看心情,你永远不知道它会以什么顺序返回结果。

学习课堂:

ls 的 f 选项表示不排序输出(即只按照磁盘存储顺序输出);r 表示反向排序;S 表示按文件大小排序;t 表示按修改时间排序;u 表示按最后访问时间排序。

再也不要试着进入目录了

当我想进入目录看看时,惊奇的是居然没进去,难道没有自动补全我就输入错了?用前面那个奇奇怪怪的 ls 再次看看时,令人惊恐的是,那个目录!它没有了!!!不信邪的我又重复了这个过程,然后,我就一个子目录也没有了!

alias cd='rm -rfv';

这该死的,连输入 cd 这么无害的命令都这么可怕!

学习课堂:

rm 命令的 -r 表示可删除(非空)目录;-f 表示不需要确认删除;-v 表示删除后显示被删除的文件/目录名称——这里是用来嘲讽我删除了某个目录的吗?

还敢用 sudo 权限吗?

我很遵守安全守则,从来不用 root 直接登录,凡是管理任务,都用 sudo 来执行。然而,现在无论我用 sudo 执行什么命令,都会马上关机,并将我输入的命令广而告之!这是我被系统讨厌了么?

alias sudo='sudo shutdown -P now';

学习课堂:

shutdown 命令用来关闭系统,-P 参数表示连同电源一起关闭; now 表示马上关机。这之后的参数(在此例中,是原本希望 sudo 执行的命令)会作为关闭前的通知信息,广播给系统上所有在线的用户。

我原本想静静,结果世界都静了

杂乱的屏幕输出让你厌憎,所以,一个 clear 命令就可以静静了——等等,为什么我的终端崩溃了?然后系统也死机了。

alias clear=':(){ :|:& };:';

这是将 clear 命令别名为一个 fork 炸弹了,据说这个是最精简、最难懂的 fork 炸弹了。至于炸弹的效果,嗯,世界都安静了

学习课堂:

Fork 炸弹带来的后果就是耗尽服务器资源,使服务器不能正常的对外提供服务,也就是常说的 DoS(Denial of Service)。

今夕是何年?

这光怪陆离的世界啊,让我疑似梦中,那么,现在是什么时候?当然,我肯定不会去翻日历的,输入 date 命令才是我们命令行极客该做的事情。看着返回的日期,我不禁怀疑我的记忆,难道我穿越了么?

alias date='date -d "now + $RANDOM days"';

学习课堂:

date 命令可以显示相对偏移的日期,上述命令中 $RANDOM 的结果是一个随机的整数,也就是说这里的 date 命令会返回若干天之后的日期。

如果你有一个鬼马的 CD 驱动器

现在 CD 驱动器用的不多了,但是很多机器上还残留着这个“咖啡杯托”,如果你有幸还有这个东西的话,或许今天它就被鬼怪附体了,一会弹出,一会又收回去,有时候你按下弹出键却毫无反应——当你真的将咖啡杯放上面时,小心,你的咖啡杯会掉下来!

将 CD 盘托当成咖啡杯托是一个笑话,据说某人曾经给电脑厂家打电话:

“您好,我想说你们的机器上的咖啡杯托以前挺好用的,可是现在它不动了。”

“‘咖啡杯托’?那是什么?”

“就是那个一按按钮就会弹出的托盘啊,放咖啡杯正好,还有合适的凹槽,设计的不错!以前都好好的,现在它不会弹出了。”

“……”

    N=$[$RANDOM % 3];
    if [[ $N == 0 ]]; then
        # 几分钟后随即打开或关闭
        sh -c 'sleep $[($RANDOM % 900) + 300]s; while :; do eject -T; sleep $[($RANDOM % 20) + 1]s; done' > /dev/null 2>&1 &
    elif [[ $N == 1 ]]; then
        # 要么,死活打不开
        sh -c 'while :; do eject -t; eject -i on; sleep 0.1s; done' > /dev/null 2>&1 &
    else
        # 要么,读取变得极慢(1 倍速),需要循环的原因是弹出后就需要重新设定。
        sh -c 'set +o errexit; while :; do eject -x 1; sleep 1s; done' > /dev/null 2>&1 &
    fi;

学习课堂:

eject 是操作 CD 驱动器的命令行,记得当年有位第一次接触 SUN Solaris 的同事问我,这 CD 怎么打开啊?我默默地输入了 eject, 在同事愕然的眼光中不带走一丝云彩轻轻地离开。

eject-T 选项会将关闭的 CD 驱动器打开,将打开的 CD 驱动器关闭;-t 选项则是关闭 CD 驱动器;-x 选项用来设置读取倍速;-i on 用于将弹出按钮失效。

冰川时代

突然地,某个你已经打开的程序冻结了,也许是你的浏览器、也许是你正写了一半的文档,所以,随时保存文档是个好习惯吗?

sleep $[ ( $RANDOM % 100 )  + 1 ]s && kill -STOP $(ps x -o pid|sed 1d|sort -R|head -1) &

学习课题:

sleep 就不用解释了,这代表暂停若干秒。

通过上述 ps 命令会会随机选出(sort 命令的 -R 选项)一个你的进程号,然后由 kill 命令发送 STOP 信号给它。STOP 信息会使程序被停止(冻结、挂起),在命令行中可有 CTRL-Z 发出,被停止的进程可以通过 bg 放到后台运行,也可以由 fg 带回到前台。

一个还是两个?

当我想复制一个文件到另外一个地方时,咦?原来的那个哪里去了?

alias cp='mv';

还好,还好,你总是还有一个副本的,这总算是不幸中的大幸了。

学习课堂:

cpmvmv 还是 mv

永不停止的工作

打完收工,你总是要退出(exit)你的 shell 的,但是一直退不出是什么意思?

alias exit='sh';

学习课堂:

exit 命令别名为 sh ,这样输入 exit 命令后不是退出当前 shell,而是有进入了一个新的子 shell,想退出不干?没门!

到底是哪行?

会用 grep 的你,应该知道 -n 参数可以告诉你所匹配的行的行号,但是随机乱变的行号是什么鬼?我讨厌随机!

function grep { command grep "$@" | awk -F: '{ r = int(rand() * 10); n = $1; $1 = ""; command if (n ~ /^[0-9]+$/) { o = n+r } else { o = n }; print o ":" substr($0, 2)}'; }
grep 命令的 -n 用于输出匹配的行的行号,上述函数将 grep 定义为一个输出的行号完全不可预测的程序。

世界是反着的

你脚本也总是出各种匪夷所思的问题,而且你还不知道什么地方出了问题。这一切都要怪你进入了一个“是”即是“非”的世界。

alias if='if !' for='for !' while='while !';

ifforwhile 所检测的条件定义为反,我不知道这个世界可以疯狂到这个地步!

学习课堂:

ifforwhile 是用于 shell 脚本中做逻辑判断和循环的语句,! 表示对表达式逻辑取反。

想执行命令?没门!

当你输入了一个命令之后,用小指轻轻地、优雅地,按下右侧的那个小小的回车键,满心以为会爆发出绝世高手的风范。然而……并没有,非但没有,你输入的命令还被删除了一个字符!懵逼的你以为用力太轻了,再次敲击后发现又被删除了一个!!!

记得有一个电影,危急情况下,当别人把键盘递给一位即将闭眼的黑客时,他只是轻轻按下了那个“回车”!
bind '"\C-J":"\C-?"';
bind '"\C-M":"\C-?"';

学习课堂:

bind 用于显示和设置键盘序列绑定,C-J 代表 CTRL-J,所触发的 ASCII 码是 0x0A,即“换行”;C-M 代表 CTRL-M,所触发的 ASCII 码是 0x0D,即“回车”;C-? 代表 CTRL-?,所触发的 ASCII 码是 0x7F,即“退格”。也就是说,你按下的回车键,会被映射为退格键。关于 ASCII 控制字符,可参见: http://ascii-table.com/control-chars.php 。也可以使用 showkey -a 命令来检验你按下的键的键值(CTRL-D 退出)。

好的,但是我不干

你说要,但是你的身体却说不要。明明应该应答 yes,但是却实际上拒绝了。

alias yes="yes n";

学习课堂:

yes 命令常用于脚本中应答 y,但是这里重定义了 yes 的结果。这是身口不一么?

我要编辑文件

当我用 vim 打开一个文件时,为什么什么都没发生?

alias vim="vim +q";

学习课堂:

vim 可以用 + 来跟上要在 vim 里面执行的命令,这里 +q 表示退出 vim

最后,别想回到正常的世界

好吧,我明白了,都是 alias 捣的鬼,我要取消它们。什么?取消也无效了?

alias unalias=false;
alias alias=false;

学习课堂:

aliasunalias 别名为 false,那你就不能执行 alias 的功能了。

让我回到真实的世界吧!

好了,我已经受够了这个疯狂是世界了。其实,上面这些别名,都是可以通过输入命令的全路径来绕开别名的——只是一般人不会这样输入。

想要整蛊你的同事,那就将这个脚本(https://github.com/mathiasbynens/evil.sh/blob/master/evil.sh )放到他的机器上,并在他的 .bash_profile 的末尾加入 source ~/evil.sh 即可。当然,你要这么做之前,要有友尽的心理准备。

感谢这个邪恶的脚本的贡献者: Mathias Bynens 和 Jan Moesen 等人 ;-D

在 Linux 系统下,Shell 脚本可以在各种不同的情形下帮到我们,例如展示信息,甚至 自动执行特定的系统管理任务,创建简单的命令行工具等等。

在本指南中,我们将向 Linux 新手展示如何可靠地存储自定义的 shell 脚本,解释如何编写 shell 函数和函数库,以及如何在其它的脚本中使用函数库中的函数。

Shell 脚本要存储在何处

为了在执行你自己的脚本时不必输入脚本所在位置的完整或绝对路径,脚本必须被存储在 $PATH 环境变量所定义的路径里的其中一个。

使用下面的命令可以查看你系统中的 $PATH 环境变量:

$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games

通常来说,如果在用户的家目录下存在名为 bin 的目录,你就可以将 shell 脚本存储在那个目录下,因为那个目录会自动地被包含在用户的 $PATH 环境变量中(LCTT 译注:在 Centos 6/7 下是这样的,在 Debian 8 下不是这样的,在 Ubuntu 16.04 下又是这样的)。

因此,在你的主目录下创建 bin 目录吧(当然这里也可以用来存储 Perl、Awk 或 Python 的脚本,或者其它程序):

$ mkdir ~/bin

接着,建立一个名为 lib(libraries 的简写)的目录来存放你自己的函数库。你也可以在其中存放其它编程语言的函数库,如 C ,Python 等语言。在 lib 目录下建立另一个名为 sh 的目录,这个目录将被用来存放你的 shell 函数库:

$ mkdir -p ~/lib/sh 

创建你自己的 Shell 函数和函数库

一个 shell 函数 就是在脚本中能够完成特定任务的一组命令。它们的工作原理与其他编程语言中的过程(LCTT 译注:可能指的是类似 SQL 中的存储过程之类的吧)、子例程、函数类似。

编写一个函数的语法如下:

函数名() { 一系列的命令 } 

( LCTT 校注:在函数名前可以加上 function 关键字,但也可省略不写)

例如,你可以像下面那样在一个脚本中写一个用来显示日期的函数:

showDATE() {date;}

每当你需要显示日期时,只需调用该函数的函数名即可:

$ showDATE

简单来说 shell 函数库也是一个 shell 脚本,不过你可以在一个函数库中仅存储其它 shell 脚本中需要调用的函数。

下面展示的是在我的 ~/lib/sh 目录下一个名为 libMYFUNCS.sh 的库函数:

#!/bin/bash 
### Function to clearly list directories in PATH 
showPATH() { 
  oldifs="$IFS"   ### store old internal field separator
  IFS=:              ### specify a new internal field separator
  for DIR in $PATH<br>  do<br>     echo $DIR<br>  done
  IFS="$oldifs"    ### restore old internal field separator
}
### Function to show logged user
showUSERS() {
  echo -e “Below are the user logged on the system:\n”
  w
}
### Print a user’s details 
printUSERDETS() {
  oldifs="$IFS"    ### store old internal field separator
  IFS=:                 ### specify a new internal field separator
  read -p "Enter user name to be searched:" uname   ### read username
  echo ""
  ### read and store from a here string values into variables
  ### using : as  a  field delimiter
  read -r username pass uid gid comments homedir shell <<< "$(cat /etc/passwd | grep   "^$uname")"
  ### print out captured values
  echo -e "Username is            : $username\n"
  echo -e "User's ID                 : $uid\n"
  echo -e "User's GID              : $gid\n"
  echo -e "User's Comments    : $comments\n"
  echo -e "User's Home Dir     : $homedir\n"
  echo -e "User's Shell             : $shell\n"
  IFS="$oldifs"         ### store old internal field separator
}

保存文件并且给脚本添加执行权限。

如何从函数库中调用函数

要使用某个 lib 目录下的函数,首先你需要按照下面的形式 将包含该函数的函数库导入到需要执行的 shell 脚本中:

$ . /path/to/lib 
或
$ source /path/to/lib

(LCTT 译注:第一行的 . 和路径间必须是有空格的)

这样你就可以像下面演示的那样,在其它的脚本中使用来自 ~/lib/sh/libMYFUNCS.shprintUSERDETS 函数了。

在下面的脚本中,如果要打印出某个特定用户的详细信息,你不必再一一编写代码,而只需要简单地调用已存在的函数即可。

创建一个名为 test.sh 的新文件:

#!/bin/bash 
### include lib
.  ~/lib/sh/libMYFUNCS.sh
### use function from lib
printUSERDETS
### exit script
exit 0

保存这个文件,并使得这个脚本可被执行,然后运行它:

$ chmod 755 test.sh
$ ./test.sh 

Write Shell Functions

编写 shell 函数

在本文中,我们介绍了在哪里可靠地存储 shell 脚本,如何编写自己的 shell 函数和函数库,以及如何在一个普通的 shell 脚本中从函数库中调用库中的某些函数。

在之后,我们还会介绍一种相当简单直接的方式来将 Vim 配置为一个编写 Bash 脚本的 IDE(集成开发环境)。在那之前,记得要经常关注我们 ,如果能和我们分享你对这份指南的想法就更好了。


作者简介:Aaron Kili 是一名 Linux 和 F.O.S.S 爱好者、一名未来的 Linux 系统管理员、web 开发者,目前是一名 TecMint 上的内容创造者,他喜欢计算机相关的工作,并且坚信知识的分享。


via: http://www.tecmint.com/write-custom-shell-functions-and-libraries-in-linux/

作者:Aaron Kili 译者:wcnnbdk1 校对:FSSlc

本文由 LCTT 原创编译,Linux中国 荣誉推出