标签 Bash 下的文章

这个世界上有两种 Linux 用户:敢于冒险的和态度谨慎的。

其中一类用户总是本能的去尝试任何能够戳中其痛点的新选择。他们尝试过不计其数的窗口管理器、系统发行版和几乎所有能找到的桌面插件。

另一类用户找到他们喜欢的东西后,会一直使用下去。他们往往喜欢所使用的系统发行版的默认配置。最先熟练掌握的文本编辑器会成为他们最钟爱的那一个。

作为一个使用桌面版和服务器版十五年之久的 Linux 用户,比起第一类来,我无疑属于第二类用户。我更倾向于使用现成的东西,如此一来,很多时候我就可以通过文档和示例方便地找到我所需要的使用案例。如果我决定选择使用非费标准的东西,这个切换过程一定会基于细致的研究,并且前提是来自好基友的大力推荐。

但这并不意味着我不喜欢尝试新事物并且查漏补失。所以最近一段时间,在我不假思索的使用了 bash shell 多年之后,决定尝试一下另外四个 shell 工具:ksh、tcsh、zsh 和 fish。这四个 shell 都可以通过我所用的 Fedora 系统的默认库轻松安装,并且他们可能已经内置在你所使用的系统发行版当中了。

这里对它们每个选择都稍作介绍,并且阐述下它适合做为你的下一个 Linux 命令行解释器的原因所在。

bash

首先,我们回顾一下最为熟悉的一个。 GNU Bash,又名 Bourne Again Shell,它是我这些年使用过的众多 Linux 发行版的默认选择。它最初发布于 1989 年,并且轻松成长为 Linux 世界中使用最广泛的 shell,甚至常见于其他一些类 Unix 系统当中。

Bash 是一个广受赞誉的 shell,当你通过互联网寻找各种事情解决方法所需的文档时,总能够无一例外的发现这些文档都默认你使用的是 bash shell。但 bash 也有一些缺点存在,如果你写过 Bash 脚本就会发现我们写的代码总是得比真正所需要的多那么几行。这并不是说有什么事情是它做不到的,而是说它读写起来并不总是那么直观,至少是不够优雅。

如上所述,基于其巨大的安装量,并且考虑到各类专业和非专业系统管理员已经适应了它的使用方式和独特之处,至少在将来一段时间内,bash 或许会一直存在。

ksh

KornShell,或许你对这个名字并不熟悉,但是你一定知道它的调用命令 ksh。这个替代性的 shell 于 80 年代起源于贝尔实验室,由 David Korn 所写。虽然最初是一个专有软件,但是后期版本是在 Eclipse Public 许可下发布的。

ksh 的拥趸们列出了他们觉得其优越的诸多理由,包括更好的循环语法,清晰的管道退出代码,处理重复命令和关联数组的更简单的方式。它能够模拟 vi 和 emacs 的许多行为,所以如果你是一个重度文本编辑器患者,它值得你一试。最后,我发现它虽然在高级脚本方面拥有不同的体验,但在基本输入方面与 bash 如出一辙。

tcsh

tcsh 衍生于 csh(Berkely Unix C shell),并且可以追溯到早期的 Unix 和计算机时代开始。

tcsh 最大的卖点在于它的脚本语言,对于熟悉 C 语言编程的人来说,看起来会非常亲切。tcsh 的脚本编写有人喜欢,有人憎恶。但是它也有其他的技术特色,包括可以为 aliases 添加参数,各种可能迎合你偏好的默认行为,包括 tab 自动完成和将 tab 完成的工作记录下来以备后查。

tcsh 以 BSD 许可发布。

zsh

zsh 是另外一个与 bash 和 ksh 有着相似之处的 shell。诞生于 90 年代初,zsh 支持众多有用的新技术,包括拼写纠正、主题化、可命名的目录快捷键,在多个终端中共享同一个命令历史信息和各种相对于原来的 bash 的轻微调整。

虽然部分需要遵照 GPL 许可,但 zsh 的代码和二进制文件可以在一个类似 MIT 许可证的许可下进行分发; 你可以在 actual license 中查看细节。

fish

之前我访问了 fish 的主页,当看到 “好了,这是一个为 90 后而生的命令行 shell” 这条略带调侃的介绍时(fish 完成于 2005 年),我就意识到我会爱上这个交互友好的 shell 的。

fish 的作者提供了若干切换过来的理由,这些理由有点小幽默并且能戳中笑点,不过还真是那么回事。这些特性包括自动建议(“注意, Netscape Navigator 4.0 来了”,LCTT 译注:NN4 是一个重要版本。),支持“惊人”的 256 色 VGA 调色,不过也有真正有用的特性,包括根据你机器上的 man 页面自动补全命令,清除脚本和基于 web 界面的配置方式。

fish 的许可主要基于 GPLv2,但有些部分是在其他许可下的。你可以查看资源库来了解完整信息

如果你想要寻找关于每个选择确切不同之处的详尽纲要,这个网站应该可以帮到你。

我的立场到底是怎样的呢?好吧,最终我应该还是会重新投入 bash 的怀抱,因为对于大多数时间都在使用命令行交互的人来说,切换过程对于编写高级的脚本能带来的好处微乎其微,并且我已经习惯于使用 bash 了。

但是我很庆幸做出了敞开大门并且尝试新选择的决定。我知道门外还有许许多多其他的东西。你尝试过哪些 shell,更中意哪一个?请在评论里告诉我们。


via: https://opensource.com/business/16/3/top-linux-shells

作者:Jason Baker 译者:mr-ping 校对:wxy

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

Bash 的 LOGO 创建以来已经有大约 20 年了没有变过了!现在是换一个新的的时候了。

Bash 当前的维护者 Chet Ramey 在邮件列表里面说:

有人给这个项目创建了新 LOGO,这位慷慨的贡献者—— Justin Dorfman ——很有耐心地让我从几个都很棒的新 LOGO 中选择一个(这是这件事里面最困难的部分了)。

所以我们将这三个设计图缩小,放到网上让 Bash 社区来选择一个胜出者。

快来帮 Bash 选一个新的吧!

快来帮 Bash 投票新 LOGO 吧!

投票地址是: http://goo.gl/forms/qjohwvtgys (显然你需要自行翻越高山,跨过大海~)。

话说,在这之前,我都从来没注意过 Bash 居然有 LOGO (很多人也对此很吃惊)!经过一番查找,原来是这个,它出现在 Bash 现在的维护者 Chet Ramey 自己维护的另外一个主页上:

Bash 现在的 LOGO

浓浓的上世纪风格啊,看起来像是 GIMP 做的艺术字,确实需要换一个新的啦!

Bash 小史

在 Richard Stallman 对之前的开发者的进度很不满意之后,Brian Fox "Brian Fox (computer programmer)") 于 1988/1/10 开始编写 Bash。FSF 和 Stallman 认为一个完全自由的系统应该有一个可以运行现有的 shell 脚本的自由 shell,所以,Bash 是 FSF 少数自己赞助的项目之一。事实上,Brian Fox 是以 FSF 雇员的身份开展这项工作的。Brian Fox 在 1989/6/8 释出了一个 beta 版本:0.99,并在1992年中期至1994年中期都是 Bash 的主要维护者。在后来他被 FSF 解雇后,维护的任务才交给了另外一个早期贡献者 Chet Ramey。(内容译自维基百科#History))

2014/9/24,一位工作在英国的 Unix/Linux 方面的网络与电信专家发现了这个程序中的一个重大安全漏洞,这就是闻名整个世界的 破壳漏洞 Shellcode ,这个漏洞的编号是 CVE-2014-6271CVE-2014-6277CVE-2014-7169。这个漏洞之所以被认为严重,是因为使用 Bash 的 CGI 脚本容易被攻击,可以执行攻击者的任意代码。(内容译自维基百科#History))

在 Linux 系统中,当你输入一个命令,再按两次 TAB 键,就会列出所有以你输入字符开头的可用命令。这并不新鲜,可能你已经知道了。这个功能被称作 命令行补全 bash completion 。默认情况下,bash 命令行可以自动补全文件或目录名称。不过,我们可以增强 bash 命令补全功能,通过 complete 命令让它达到新的高度。

这个教程说明了我们是怎样使用 可编程的命令行补全功能 programmable completion 把自动补全功能应用于选项或者命令行参数。

例如:在输入 write 命令之后,如果你按两次 TAB 按键,自动补全功能会提供可供执行 write 操作的用户列表。

$ write [TAB][TAB]
bala      raj
jason     randy
john      ritu
mayla     thomas
nisha     www-data

在下面的例子中,可以为 telnet 命令显示可用的主机名:

$ telnet [TAB][TAB]
localhost  dev-db  fileserver

要让可编程命令补全功能在你的终端起作用 ,你只需要如下执行/etc/bash_completion即可:

# . /etc/bash_completion

你也可以取消/etc/bash.bashrc(来自 Ubuntu Linux 13.04 系统)中如下的注释,这样,你就可以不需要执行上面的命令了:

### enable bash completion in interactive shells
if ! shopt -oq posix; then
  if [ -f /usr/share/bash-completion/bash_completion ]; then
    . /usr/share/bash-completion/bash_completion
  elif [ -f /etc/bash_completion ]; then
    . /etc/bash_completion
  fi
fi

如果你没有发现这些代码,也没有找到/etc/bash_completion文件,那么你只需要通过使用apt-get命令来安装bash\_completion 包即可。

1. 查看已有的命令行补全

在启用可编程的命令行补全功能后,就已经有了一些定义好的命令补全功能。complete 命令用于定义命令行补全。

要查看已有的命令行补全,如下使用 complete 命令:

complete -p | less

上面例子中的 -p 选项是可选的。

2. 列出 bash 中标准补全功能

默认情况下,Bash 为 Linux 用户提供了下列标准补全功能。

  1. 变量补全
  2. 用户名补全
  3. 主机名补全
  4. 路径补全
  5. 文件名补全

我们在之前的 bash 标准补全中讨论过这些。

3. 定义一个命令名补全

通过 -c 选项可以将所有的可用命令作为一个命令的补全参数。在下面的例子里面,为 which 命令定义了一个补全(LCTT译注:在按两下 TAB 时,可以列出所有命令名作为可补全的参数)。

$ complete -c which

$ which [TAB][TAB]
Display all 2116 possibilities? (y or n)

如上,如果按下 ‘y’,就会列出所有的命令名。

4. 定义一个目录补全

通过选项 -d,可以定义一个仅包含目录名的补全参数。在下面的例子中,为 ls 命令定义了补全。

$ ls
countfiles.sh  dir1/          dir2/          dir3/

$ complete -d ls

$ ls [TAB][TAB]
dir1/          dir2/          dir3/

如上,连按下 TAB 仅会显示目录名。

5. 定义一个后台任务名补全

补全功能也能够以任务名作为补全参数。选项 -j 可以定义任务名作为传递给命令的参数,如下:

$ jobs
[1]-  Stopped                 cat
[2]+  Stopped                 sed 'p'

$ complete -j ./list_job_attrib.sh

$ ./list_job_attrib.sh [TAB][TAB]
cat   sed

关于后台任务,你可以参考 Linux 后台任务中的例子了解如何管理后台任务。

6. 带有前缀和后缀的补全

补全功能可以为实际的补全内容定义前缀和后缀。在下面的例子中,为 list\_job\_attrib.sh 定义了补全内容的前缀和后缀。

$ jobs 
[1]+  Stopped                 cat

$ complete -P '">' -S '<"' ./list_job_attrib.sh

$ ./list_job_attrib.sh [TAB][TAB]

$ ./list_job_attrib.sh ">cat<"

7. 带有排除的文件名和目录名补全

假如脚本运行完成后,输出目录如下:

$ cd output/

$ ls
all_calls.txt   incoming_calls.txt   outgoing_calls.txt   missed_calls.txt
parser_mod.tmp  extract.o

如上,如果你想要 ls 命令的补全忽略 .tmp 和 .o 文件:

$ export FIGNORE='.tmp:.o'

$ complete -f -d ls

$ cd output

$ ls [TAB][TAB]
all_calls.txt   incoming_calls.txt   outgoing_calls.txt   missed_calls.txt

FIGNORE 是一个环境变量,它包含了自动补全所需要排除的文件名后缀。

8. 通过 IFS 变量分割字符串得到补全值

可以通过 -W 选项定义补全值列表,然后通过 IFS 环境变量进行切分。切分结果会展开变量并作为补全显示。

$ export IFS=" "

$ complete -W "bubble quick" ./sort_numbers.sh

$ ./sort_numbers.sh [TAB][TAB]
bubble   quick

如上所述,字符串通过 IFS 分隔符进行切分后,内嵌的变量会被展开为变量值,所以可以如下使用变量:

$ echo $SORT_TYPE1
bubble

$ echo $SORT_TYPE2
quick

$ complete -W "$SORT_TYPE1 $SORT_TYPE2" ./sort_numbers.sh
$ ./sort_numbers.sh [TAB][TAB]
bubble   quick

9. 写个函数来生成补全

你可以引入一个函数来定义补全。使用 -F 选项将函数名传给 complete 命令,执行函数生成补全内容。例如,函数如下:

_parser_options()
{
  local curr_arg;

  curr_arg=${COMP_WORDS[COMP_CWORD]}

  COMPREPLY=( $(compgen -W '-i --incoming -o --outgoing -m --missed' -- $curr_arg ) );
}

在上述函数中:

  1. COMPREPLY : 该数组控制连按下 TAB 后显示的结果
  2. COMP\_WORDS : 该数组包含命令行输入的单词
  3. COMP\_CWORD : COMP\_WORDS 数组的索引,使用它来区分命令行可以访问的单词位置
  4. compgen : -W 基于 $current\_arg 提供可能的补全及其参数

该函数放在 parser\_option 文件中,并通过 source 命令引入:

$ source parser_option

将该函数和你的 parser.pl 脚本关联起来:

$ complete -F _parser_options ./parser.pl

$ ./parser.pl [TAB][TAB]
-i       --incoming       -o       --outgoing       -m       --missed

如上,parser.pl 的选项是由函数 \_parser\_options() 生成的。

提示: 查看/etc/bash_completion 来了解更多的可编程补全函数。

10. 当第一个规则没有生成结果时,就使用第二个

如果定义的补全规则没有生成匹配时,可以使用 -o 选项生成补全。

$ complete -F _count_files -o dirnames ./countfiles.sh

如上,为 ./countfiles.sh 定义了 \_count\_files 补全函数。 如果 the \_count\_files() 函数没有生成任何匹配的话,就会触发目录补全。

$ ls 
countfiles.sh    dir1/      dir2/      dir3/

$./countfiles.sh [TAB][TAB]
dir1    dir2    dir3

在 Linux 或类 UNIX 系统下如何使用 KSH 或 BASH shell 逐行读取一个文件?

在 Linux、OSX、 *BSD 或者类 Unix 系统下你可以使用 ​​while..do..done 的 bash 循环来逐行读取一个文件。

在 Bash Unix 或者 Linux shell 中逐行读取一个文件的语法

对于 bash、ksh、 zsh 和其他的 shells 语法如下

while read -r line; do COMMAND; done < input.file

通过 -r 选项传递给 read 命令以防止阻止解释其中的反斜杠转义符。

在 read 命令之前添加 IFS= 选项,来防止首尾的空白字符被去掉。

while IFS= read -r line; do COMMAND_on $line; done < input.file

这是更适合人类阅读的语法:

#!/bin/bash
input="/path/to/txt/file"
while IFS= read -r var
do
  echo "$var"
done < "$input"

示例

下面是一些例子:

#!/bin/ksh
file="/home/vivek/data.txt"
while IFS= read line
do
    # display $line or do somthing with $line
    echo "$line"
done <"$file"

在 bash shell 中相同的例子:

#!/bin/bash
file="/home/vivek/data.txt"
while IFS= read -r line
do
    # display $line or do somthing with $line
    printf '%s\n' "$line"
done <"$file"

你还可以看看这个更好的:

#!/bin/bash
file="/etc/passwd"
while IFS=: read -r f1 f2 f3 f4 f5 f6 f7
do
    # display fields using f1, f2,..,f7
    printf 'Username: %s, Shell: %s, Home Dir: %s\n' "$f1" "$f7" "$f6"
done <"$file"

示例输出:

Fig.01: Bash shell scripting- read file line by line demo outputs

图01:Bash 脚本:读取文件并逐行输出文件

Bash 脚本:逐行读取文本文件并创建为 pdf 文件

我的输入文件如下(faq.txt):

4|http://www.cyberciti.biz/faq/mysql-user-creation/|Mysql User Creation: Setting Up a New MySQL User Account
4096|http://www.cyberciti.biz/faq/ksh-korn-shell/|What is UNIX / Linux Korn Shell?
4101|http://www.cyberciti.biz/faq/what-is-posix-shell/|What Is POSIX Shell?
17267|http://www.cyberciti.biz/faq/linux-check-battery-status/|Linux: Check Battery Status Command
17245|http://www.cyberciti.biz/faq/restarting-ntp-service-on-linux/|Linux Restart NTPD Service Command
17183|http://www.cyberciti.biz/faq/ubuntu-linux-determine-your-ip-address/|Ubuntu Linux: Determine Your IP Address
17172|http://www.cyberciti.biz/faq/determine-ip-address-of-linux-server/|HowTo: Determine an IP Address My Linux Server
16510|http://www.cyberciti.biz/faq/unix-linux-restart-php-service-command/|Linux / Unix: Restart PHP Service Command
8292|http://www.cyberciti.biz/faq/mounting-harddisks-in-freebsd-with-mount-command/|FreeBSD: Mount Hard Drive / Disk Command
8190|http://www.cyberciti.biz/faq/rebooting-solaris-unix-server/|Reboot a Solaris UNIX System

我的 bash 脚本:

#!/bin/bash
# Usage: Create pdf files from input (wrapper script)
# Author: Vivek Gite <Www.cyberciti.biz> under GPL v2.x+
#---------------------------------------------------------

#Input file
_db="/tmp/wordpress/faq.txt"

#Output location
o="/var/www/prviate/pdf/faq"

_writer="~/bin/py/pdfwriter.py"

# If file exists 
if [[ -f "$_db" ]]
then
    # read it
    while IFS='|' read -r pdfid pdfurl pdftitle
    do
        local pdf="$o/$pdfid.pdf"
        echo "Creating $pdf file ..."
    #Genrate pdf file
        $_writer --quiet --footer-spacing 2 \
        --footer-left "nixCraft is GIT UL++++ W+++ C++++ M+ e+++ d-" \
        --footer-right "Page [page] of [toPage]" --footer-line \
        --footer-font-size 7 --print-media-type "$pdfurl" "$pdf"
    done <"$_db"
fi

技巧:从 bash 变量中读取

让我们看看如何在 Debian 或者 Ubuntu Linux 下列出所有安装过的 php 包,请输入:

# 我将输出内容赋值到一个变量名为 $list中 #

list=$(dpkg --list php\* | awk '/ii/{print $2}')
printf '%s\n' "$list"

示例输出:

php-pear
php5-cli
php5-common
php5-fpm
php5-gd
php5-json
php5-memcache
php5-mysql
php5-readline
php5-suhosin-extension

你现在可以从 $list 中看到它们,并安装这些包:

#!/bin/bash
# BASH can iterate over $list variable using a "here string" #
while IFS= read -r pkg
do
    printf 'Installing php package %s...\n' "$pkg"
    /usr/bin/apt-get -qq install $pkg
done <<< "$list"
printf '*** Do not forget to run php5enmod and restart the server (httpd or php5-fpm) ***\n'

示例输出:

Installing php package php-pear...
Installing php package php5-cli...
Installing php package php5-common...
Installing php package php5-fpm...
Installing php package php5-gd...
Installing php package php5-json...
Installing php package php5-memcache...
Installing php package php5-mysql...
Installing php package php5-readline...
Installing php package php5-suhosin-extension...

*** Do not forget to run php5enmod and restart the server (httpd or php5-fpm) ***

via: http://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/

作者: VIVEK GIT 译者:strugglingyouth 校对:wxy

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

Linux 用户今天又得到了一个“惊喜”! Red Hat 安全团队在 Linux 中广泛使用的 Bash shell 中发现了一个隐晦而危险的安全漏洞。该漏洞被称作“Bash Bug”或“Shellshock”。

当用户正常访问,该漏洞允许攻击者的代码像在 shell 中一样执行,这就为各种各样的攻击打开了方便之门。而且,更糟糕的是该漏洞已经在 Linux 中存在很久了,所以修补某个 Linux 机器很容易,但是要全部修补,几乎不可能完成。

Red Hat 和 Fedora 已经发布了针对该漏洞的修补程序。该漏洞也会影响 OS X,不过苹果公司尚未发布正式的修补程序。

这个 Bash 漏洞可能比 Heartbleed 更危险。
— — Robert Graham (@ErrataRob) 2014 年 9 月 24 日

Red Hat 的 Errata Security 团队的 Robert David Graham 比较了这个安全漏洞和 Heartbleed 的风险,该漏洞分布更广泛,有可能对系统安全带来长期影响。Graham 在一篇博客文章中写道,“有大量的软件以某种方式与 shell 交互,我们没有办法列举受到该漏洞影响的所有软件。”据 Verge 得到的消息,Berkeley ICSI 的研究员Nicholas Weaver 也悲观的同意这个说法:“它很隐晦、很可怕,并且将会伴随我们多年。”

网络安全公司Rapid7工程部经理 Tod Beardsley 警告称,Bash漏洞的严重级别为“10”,意味着它对用户电脑的威胁最大。Bash漏洞的利用复杂度级别为“低”,意味着黑客可以相对轻松地利用它发动攻击。

另有网络安全公司Trail of Bits的CEO Dan Guido表示,“Heartbleed”漏洞能够允许黑客监控用户电脑,但不会取得控制权。而利用Bash漏洞的方法也更简单——只需要剪切和粘贴一行代码即可。

据称,谷歌安全研究员 Tavis Ormandy 在Twitter上表示,Linux系统提供商推出的补丁似乎“并不完整”,这引发了几位安全专家的担忧。

已知受到影响的 Linux 发行版和软件包括:

  • Ubuntu Ubuntu Linux 12.04 LTS i386/amd64
  • Ubuntu Ubuntu Linux 10.04 sparc/powerpc/i386/ARM/amd64
  • GNU GNU bash 3.1.4/3.0.16/4.2/4.1/4.0 RC1/4.0/3.2.48/3.2/3.00.0(2)/3.0
  • Debian Linux 6.0 sparc/s390/powerpc/mips/ia-64/ia-32/arm/amd64
  • CentOS CentOS 5

更多技术细节,可以参考:

以前我总想知道如何为我的Bash脚本创建命令行参数。经过搜索,我发现了2个函数可以处理这个问题,getopt 函数和 getopts 函数。我无意争论哪一个函数更好的。getopts 是一个shell内建命令,而且似乎比 getopt 更容易实现这个功能,所以在这篇文章里我准备讲讲getopts。

bash getopts

开始的时候,我只试着处理传递给脚本的命令行参数。最后,我添加了另外一些有用的功能函数,使得这个脚本可以成为其他任何交互式脚本处理命令行的开始模板。我还添加了一个纯文本格式的帮助函数,让脚本更加容易阅读。

与其来一长段文字解释 getopts 在bash中是如何工作的,我认为不如直接来一个能工作的脚本更让人觉得轻松一些。

#!/bin/bash

######################################################################
#This is an example of using getopts in Bash. It also contains some
#other bits of code I find useful.
#Author: Linerd
#Website: http://tuxtweaks.com/
#Copyright 2014
#License: Creative Commons Attribution-ShareAlike 4.0
#http://creativecommons.org/licenses/by-sa/4.0/legalcode
######################################################################

#Set Script Name variable
SCRIPT=`basename ${BASH_SOURCE[0]}`

#Initialize variables to default values.
OPT_A=A
OPT_B=B
OPT_C=C
OPT_D=D

#Set fonts for Help.[译注: 这里tput用来更改终端文本属性,比如加粗,高亮等]
NORM=`tput sgr0`
BOLD=`tput bold`
REV=`tput smso`

#Help function
function HELP {
  echo -e \\n"Help documentation for ${BOLD}${SCRIPT}.${NORM}"\\n
  echo -e "${REV}Basic usage:${NORM} ${BOLD}$SCRIPT file.ext${NORM}"\\n
  echo "Command line switches are optional. The following switches are recognized."
  echo "${REV}-a${NORM}  --Sets the value for option ${BOLD}a${NORM}. Default is ${BOLD}A${NORM}."
  echo "${REV}-b${NORM}  --Sets the value for option ${BOLD}b${NORM}. Default is ${BOLD}B${NORM}."
  echo "${REV}-c${NORM}  --Sets the value for option ${BOLD}c${NORM}. Default is ${BOLD}C${NORM}."
  echo "${REV}-d${NORM}  --Sets the value for option ${BOLD}d${NORM}. Default is ${BOLD}D${NORM}."
  echo -e "${REV}-h${NORM}  --Displays this help message. No further functions are performed."\\n
  echo -e "Example: ${BOLD}$SCRIPT -a foo -b man -c chu -d bar file.ext${NORM}"\\n
  exit 1
}

#Check the number of arguments. If none are passed, print help and exit.
NUMARGS=$#
echo -e \\n"Number of arguments: $NUMARGS"
if [ $NUMARGS -eq 0 ]; then
  HELP
fi

### Start getopts code ###

#Parse command line flags
#如果选项需要后跟参数,在选项后面加":"
#注意"-h"选项后面没有":",因为他不需要参数。选项字符串最开始的":"是用来去掉来自getopts本身的报错的,同时获取不能识别的选项。(译注:如果选项字符串不以":"开头,发生错误(非法的选项或者缺少参数)时,getopts会向错误输出打印错误信息;如果以":"开头,则不会打印[在man中叫slient error reporting],同时将出错的选项赋给OPTARG变量)

while getopts :a:b:c:d:h FLAG; do
  case $FLAG in
    a)  #set option "a"
      OPT_A=$OPTARG
      echo "-a used: $OPTARG"
      echo "OPT_A = $OPT_A"
      ;;
    b)  #set option "b"
      OPT_B=$OPTARG
      echo "-b used: $OPTARG"
      echo "OPT_B = $OPT_B"
      ;;
    c)  #set option "c"
      OPT_C=$OPTARG
      echo "-c used: $OPTARG"
      echo "OPT_C = $OPT_C"
      ;;
    d)  #set option "d"
      OPT_D=$OPTARG
      echo "-d used: $OPTARG"
      echo "OPT_D = $OPT_D"
      ;;
    h)  #show help
      HELP
      ;;
    \?) #unrecognized option - show help
      echo -e \\n"Option -${BOLD}$OPTARG${NORM} not allowed."
      HELP
      #在这里如果你不想打印完整的帮助信息,只想显示简单的错误信息,去掉上面的两行,同时使用下面的两行。
      #echo -e "Use ${BOLD}$SCRIPT -h${NORM} to see the help documentation."\\n
      #exit 2
      ;;
  esac
done

shift $((OPTIND-1))  #This tells getopts to move on to the next argument.

### End getopts code ###


### Main loop to process files ###

#这里你可以用你的脚本处理逻辑来替代。这个例子只是在终端中打印文件的文件名和后缀名。你可以把任意其他的文件处理任务放到这个while-do循环中。   

while [ $# -ne 0 ]; do
  FILE=$1
  TEMPFILE=`basename $FILE`
  #TEMPFILE="${FILE##*/}"  #另外一种获取不带后缀的文件名的方法。
  FILE_BASE=`echo "${TEMPFILE%.*}"`  #file without extension
  FILE_EXT="${TEMPFILE##*.}"  #file extension


  echo -e \\n"Input file is: $FILE"
  echo "File withouth extension is: $FILE_BASE"
  echo -e "File extension is: $FILE_EXT"\\n
  shift  #Move on to next input file.
done

### End main loop ###

exit 0

将上面的代码复制到你的文本编辑器里,然后保存到你的可执行路径下。我将这个脚本命名为 options 并保存到 /home/linerd/bin 路径下。保存之后记得给你的脚本添加可执行权限。

chmod +x ~/bin/options

现在脚本已经可以运行了。试试用 -h 参数来打印帮助信息吧。

options -h

遇到不支持的选项,脚本同样可以给出提示,并打印帮助信息。

options -z

最后,getopts可以以任意的顺序处理你给的命令行参数。唯一的限制是你要处理的文件必须放在所有参数的最后。

options -d bar -c chu -b man -a foo example1.txt example2.txt

现在你可以从这些例子里看到如何通过命令行参数给脚本里的变量赋值。这个脚本里除了getopts还有很多其他的东西,但是我认为这些就足以成为一个新脚本的开头模板了。如果你有兴趣更深入地学习bash的getopts,你可以找找深埋在man page的“Builtins”这一节里的文档,也可以从 Bash Reference Manual 找到信息。

接下来呢?

你会用getops来干什么呢?在评论里告诉我吧。


via: http://tuxtweaks.com/2014/05/bash-getopts/

译者: CNprober <[email protected], QQ619913541> 校对:wxy

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