标签 脚本 下的文章

通常,当人们提到“shell脚本语言”时,浮现在他们脑海中是bash,ksh,sh或者其它相类似的linux/unix脚本语言。脚本语言是与计算机交流的另外一种途径。使用图形化窗口界面(不管是windows还是linux都无所谓)用户可以移动鼠标并点击各种对象,比如按钮、列表、选框等等。但这种方式在每次用户想要计算机/服务器完成相同任务时(比如说批量转换照片,或者下载新的电影、mp3等)却是十分不方便。要想让所有这些事情变得简单并且自动化,我们可以使用shell脚本。

某些编程语言,像pascal、foxpro、C、java之类,在执行前需要先进行编译。它们需要合适的编译器来让我们的代码完成某个任务。

而其它一些编程语言,像php、javascript、visualbasic之类,则不需要编译器,因此它们需要解释器,而我们不需要编译代码就可以运行程序。

shell脚本也像解释器一样,但它通常用于调用外部已编译的程序。然后,它会捕获输出结果、退出代码并根据情况进行处理。

Linux世界中最为流行的shell脚本语言之一,就是bash。而我认为(这是我自己的看法)原因在于,默认情况下bash shell可以让用户便捷地通过历史命令(先前执行过的)导航,与之相反的是,ksh则要求对.profile进行一些调整,或者记住一些“魔术”组合键来查阅历史并修正命令。

好了,我想这些介绍已经足够了,剩下来哪个环境最适合你,就留给你自己去判断吧。从现在开始,我将只讲bash及其脚本。在下面的例子中,我将使用CentOS 6.6和bash-4.1.2。请确保你有相同版本,或者更高版本。

Shell脚本流

shell脚本语言就跟和几个人聊天类似。你只需把所有命令想象成能帮你做事的那些人,只要你用正确的方式来请求他们去做。比如说,你想要写文档。首先,你需要纸。然后,你需要把内容说给某个人听,让他帮你写。最后,你想要把它存放到某个地方。或者说,你想要造一所房子,因而你需要请合适的人来清空场地。在他们说“事情干完了”,那么另外一些工程师就可以帮你来砌墙。最后,当这些工程师们也告诉你“事情干完了”的时候,你就可以叫油漆工来给房子粉饰了。如果你让油漆工在墙砌好前就来粉饰,会发生什么呢?我想,他们会开始发牢骚了。几乎所有这些像人一样的命令都会说话,如果它们完成了工作而没有发生什么问题,那么它们就会告诉“标准输出”。如果它们不能做你叫它们做的事——它们会告诉“标准错误”。这样,最后,所有的命令都通过“标准输入”来听你的话。

快速实例——当你打开linux终端并写一些文本时——你正通过“标准输入”和bash说话。那么,让我们来问问bash shell who am i(我是谁?)吧。

root@localhost ~]# who am i                                <--- 你通过标准输入对 bash shell 说
root     pts/0        2015-04-22 20:17 (192.168.1.123)     <--- bash shell通过标准输出回答你

现在,让我们说一些bash听不懂的问题:

[root@localhost ~]# blablabla           <--- 哈,你又在和标准输入说话了
-bash: blablabla: command not found     <--- bash通过标准错误在发牢骚了

“:”之前的第一个单词通常是向你发牢骚的命令。实际上,这些流中的每一个都有它们自己的索引号(LCTT 译注:文件句柄号):

  • 标准输入(stdin) - 0
  • 标准输出(stdout) - 1
  • 标准错误(stderr) - 2

如果你真的想要知道哪个输出命令说了些什么——你需要将那次发言重定向到(在命令后使用大于号“>”和流索引)文件:

[root@localhost ~]# blablabla 1> output.txt
-bash: blablabla: command not found

在本例中,我们试着重定向流1(stdout)到名为output.txt的文件。让我们来看对该文件内容所做的事情吧,使用cat命令可以做这事:

[root@localhost ~]# cat output.txt
[root@localhost ~]#

看起来似乎是空的。好吧,现在让我们来重定向流2(stderr):

[root@localhost ~]# blablabla 2> error.txt
[root@localhost ~]#

好吧,我们看到牢骚话没了。让我们检查一下那个文件:

[root@localhost ~]# cat error.txt
-bash: blablabla: command not found
[root@localhost ~]#

果然如此!我们看到,所有牢骚话都被记录到errors.txt文件里头去了。

有时候,命令会同时产生stdoutstderr。要重定向它们到不同的文件,我们可以使用以下语句:

command 1>out.txt 2>err.txt

要缩短一点语句,我们可以忽略“1”,因为默认情况下stdout会被重定向:

command >out.txt 2>err.txt

好吧,让我们试试做些“坏事”。让我们用rm命令把file1和folder1给删了吧:

[root@localhost ~]# rm -vf folder1 file1 > out.txt 2>err.txt

现在来检查以下输出文件:

[root@localhost ~]# cat out.txt
removed `file1'
[root@localhost ~]# cat err.txt
rm: cannot remove `folder1': Is a directory
[root@localhost ~]#

正如我们所看到的,不同的流被分离到了不同的文件。有时候,这也不是很方便,因为我们想要查看出现错误时,在某些操作前面或后面所连续发生的事情。要实现这一目的,我们可以重定向两个流到同一个文件:

command >>out_err.txt 2>>out_err.txt

注意:请注意,我使用“>>”替代了“>”。它允许我们附加到文件,而不是覆盖文件。

我们也可以重定向一个流到另一个:

command >out_err.txt 2>&1

让我来解释一下吧。所有命令的标准输出将被重定向到out\_err.txt,错误输出将被重定向到流1(上面已经解释过了),而该流会被重定向到同一个文件。让我们看这个实例:

[root@localhost ~]# rm -fv folder2 file2 >out_err.txt 2>&1
[root@localhost ~]# cat out_err.txt
rm: cannot remove `folder2': Is a directory
removed `file2'
[root@localhost ~]#

看着这些组合的输出,我们可以将其说明为:首先,rm命令试着将folder2删除,而它不会成功,因为linux要求-r键来允许rm命令删除文件夹,而第二个file2会被删除。通过为rm提供-v(详情)键,我们让rm命令告诉我们每个被删除的文件或文件夹。

这些就是你需要知道的,关于重定向的几乎所有内容了。我是说几乎,因为还有一个更为重要的重定向工具,它称之为“管道”。通过使用|(管道)符号,我们通常重定向stdout流。

比如说,我们有这样一个文本文件:

[root@localhost ~]# cat text_file.txt
This line does not contain H e l l o  word
This lilne contains Hello
This also containd Hello
This one no due to HELLO all capital
Hello bash world!

而我们需要找到其中某些带有“Hello”的行,Linux中有个grep命令可以完成该工作:

[root@localhost ~]# grep Hello text_file.txt
This lilne contains Hello
This also containd Hello
Hello bash world!
[root@localhost ~]#

当我们有个文件,想要在里头搜索的时候,这用起来很不错。当如果我们需要在另一个命令的输出中查找某些东西,这又该怎么办呢?是的,当然,我们可以重定向输出到文件,然后再在文件里头查找:

[root@localhost ~]# fdisk -l>fdisk.out
[root@localhost ~]# grep "Disk /dev" fdisk.out
Disk /dev/sda: 8589 MB, 8589934592 bytes
Disk /dev/mapper/VolGroup-lv_root: 7205 MB, 7205814272 bytes
Disk /dev/mapper/VolGroup-lv_swap: 855 MB, 855638016 bytes
[root@localhost ~]#

如果你打算grep一些双引号引起来带有空格的内容呢!

注意:fdisk命令显示关于Linux操作系统磁盘驱动器的信息。

就像我们看到的,这种方式很不方便,因为我们不一会儿就把临时文件空间给搞乱了。要完成该任务,我们可以使用管道。它们允许我们重定向一个命令的stdout到另一个命令的stdin流:

[root@localhost ~]# fdisk -l | grep "Disk /dev"
Disk /dev/sda: 8589 MB, 8589934592 bytes
Disk /dev/mapper/VolGroup-lv_root: 7205 MB, 7205814272 bytes
Disk /dev/mapper/VolGroup-lv_swap: 855 MB, 855638016 bytes
[root@localhost ~]#

如你所见,我们不需要任何临时文件就获得了相同的结果。我们把fdisk stdout重定向到了grep stdin

注意 : 管道重定向总是从左至右的。

还有几个其它重定向,但是我们将把它们放在后面讲。

在shell中显示自定义信息

正如我们所知道的,通常,与shell的交流以及shell内的交流是以对话的方式进行的。因此,让我们创建一些真正的脚本吧,这些脚本也会和我们讲话。这会让你学到一些简单的命令,并对脚本的概念有一个更好的理解。

假设我们是某个公司的总服务台经理,我们想要创建某个shell脚本来注册呼叫信息:电话号码、用户名以及问题的简要描述。我们打算把这些信息存储到普通文本文件data.txt中,以便今后统计。脚本它自己就是以对话的方式工作,这会让总服务台的工作人员的小日子过得轻松点。那么,首先我们需要显示提问。对于显示信息,我们可以用echo和printf命令。这两个都是用来显示信息的,但是printf更为强大,因为我们可以通过它很好地格式化输出,我们可以让它右对齐、左对齐或者为信息留出专门的空间。让我们从一个简单的例子开始吧。要创建文件,请使用你惯用的文本编辑器(kate,nano,vi,……),然后创建名为note.sh的文件,里面写入这些命令:

echo "Phone number ?"

如何运行/执行脚本?

在保存文件后,我们可以使用bash命令来运行,把我们的文件作为它的参数:

[root@localhost ~]# bash note.sh
Phone number ?

实际上,这样来执行脚本是很不方便的。如果不使用bash命令作为前缀来执行,会更舒服一些。要让脚本可执行,我们可以使用chmod命令:

[root@localhost ~]# ls -la note.sh
-rw-r--r--. 1 root root 22 Apr 23 20:52 note.sh
[root@localhost ~]# chmod +x note.sh
[root@localhost ~]# ls -la note.sh
-rwxr-xr-x. 1 root root 22 Apr 23 20:52 note.sh
[root@localhost ~]#

注意 : ls命令显示了当前文件夹内的文件。通过添加-la键,它会显示更多文件信息。

如我们所见,在chmod命令执行前,脚本只有读(r)和写(w)权限。在执行chmod +x后,它就获得了执行(x)权限。(关于权限的更多细节,我会在下一篇文章中讲述。)现在,我们只需这么来运行:

[root@localhost ~]# ./note.sh
Phone number ?

在脚本名前,我添加了 ./ 组合。.(点)在unix世界中意味着当前位置(当前文件夹),/(斜线)是文件夹分隔符。(在Windows系统中,我们使用反斜线 \ 表示同样功能)所以,这整个组合的意思是说:“从当前文件夹执行note.sh脚本”。我想,如果我用完整路径来运行这个脚本的话,你会更加清楚一些:

[root@localhost ~]# /root/note.sh
Phone number ?
[root@localhost ~]#

它也能工作。

如果所有linux用户都有相同的默认shell,那就万事OK。如果我们只是执行该脚本,默认的用户shell就会用于解析脚本内容并运行命令。不同的shell的语法、内部命令等等有着一丁点不同,所以,为了保证我们的脚本会使用bash,我们应该添加#!/bin/bash到文件首行。这样,默认的用户shell将调用/bin/bash,而只有在那时候,脚本中的命令才会被执行:

[root@localhost ~]# cat note.sh
#!/bin/bash
echo "Phone number ?"

直到现在,我们才100%确信bash会用来解析我们的脚本内容。让我们继续。

读取输入

在显示信息后,脚本会等待用户回答。有个read命令用来接收用户的回答:

#!/bin/bash
echo "Phone number ?"
read phone

在执行后,脚本会等待用户输入,直到用户按[ENTER]键结束输入:

[root@localhost ~]# ./note.sh
Phone number ?
12345                               <--- 这儿是我输入的内容
[root@localhost ~]#

你输入的所有东西都会被存储到变量phone中,要显示变量的值,我们同样可以使用echo命令:

[root@localhost ~]# cat note.sh
#!/bin/bash
echo "Phone number ?"
read phone
echo "You have entered $phone as a phone number"
[root@localhost ~]# ./note.sh
Phone number ?
123456
You have entered 123456 as a phone number
[root@localhost ~]#

bash shell中,一般我们使用**$**(美元)符号来表明这是一个变量,除了读入到变量和其它为数不多的时候才不用这个$(将在今后说明)。

好了,现在我们准备添加剩下的问题了:

#!/bin/bash
echo "Phone number?"
read phone
echo "Name?"
read name
echo "Issue?"
read issue
[root@localhost ~]# ./note.sh
Phone number?
123
Name?
Jim
Issue?
script is not working.
[root@localhost ~]#

使用流重定向

太完美了!剩下来就是重定向所有东西到文件data.txt了。作为字段分隔符,我们将使用/(斜线)符号。

注意 : 你可以选择任何你认为是最好的分隔符,但是确保文件内容不会包含这些符号在内,否则它会导致在文本行中产生额外字段。

别忘了使用“>>”来代替“>”,因为我们想要将输出内容附加到文件末!

[root@localhost ~]# tail -2 note.sh
read issue
echo "$phone/$name/$issue">>data.txt
[root@localhost ~]# ./note.sh
Phone number?
987
Name?
Jimmy
Issue?
Keybord issue.
[root@localhost ~]# cat data.txt
987/Jimmy/Keybord issue.
[root@localhost ~]#

注意tail命令显示了文件的最后的n行。

搞定。让我们再来运行一次看看:

[root@localhost ~]# ./note.sh
Phone number?
556
Name?
Janine
Issue?
Mouse was broken.
[root@localhost ~]# cat data.txt
987/Jimmy/Keybord issue.
556/Janine/Mouse was broken.
[root@localhost ~]#

我们的文件在增长,让我们在每行前面加个日期吧,这对于今后摆弄这些统计数据时会很有用。要实现这功能,我们可以使用date命令,并指定某种格式,因为我不喜欢默认格式:

[root@localhost ~]# date
Thu Apr 23 21:33:14 EEST 2015                     <---- date命令的默认输出
[root@localhost ~]# date "+%Y.%m.%d %H:%M:%S"
2015.04.23 21:33:18                               <---- 格式化后的输出

有几种方式可以读取命令的输出到变量,在这种简单的情况下,我们将使用`(是反引号,不是单引号,和波浪号~在同一个键位):

[root@localhost ~]# cat note.sh
#!/bin/bash
now=`date "+%Y.%m.%d %H:%M:%S"`
echo "Phone number?"
read phone
echo "Name?"
read name
echo "Issue?"
read issue
echo "$now/$phone/$name/$issue">>data.txt
[root@localhost ~]# ./note.sh
Phone number?
123
Name?
Jim
Issue?
Script hanging.
[root@localhost ~]# cat data.txt
2015.04.23 21:38:56/123/Jim/Script hanging.
[root@localhost ~]#

嗯…… 我们的脚本看起来有点丑啊,让我们来美化一下。如果你要手动读取read命令,你会发现read命令也可以显示一些信息。要实现该功能,我们应该使用-p键加上信息:

[root@localhost ~]# cat note.sh
#!/bin/bash
now=`date "+%Y.%m.%d %H:%M:%S"`
read -p "Phone number: " phone
read -p "Name: " name
read -p "Issue: " issue
echo "$now/$phone/$name/$issue">>data.txt

你可以直接从控制台查找到各个命令的大量有趣的信息,只需输入:man read, man echo, man date, man ……

同意吗?它看上去是舒服多了!

[root@localhost ~]# ./note.sh
Phone number: 321
Name: Susane
Issue: Mouse was stolen
[root@localhost ~]# cat data.txt
2015.04.23 21:38:56/123/Jim/Script hanging.
2015.04.23 21:43:50/321/Susane/Mouse was stolen
[root@localhost ~]#

光标在消息的后面(不是在新的一行中),这有点意思。(LCTT 译注:如果用 echo 命令输出显示的话,可以用 -n 参数来避免换行。)

循环

是时候来改进我们的脚本了。如果用户一整天都在接电话,如果每次都要去运行,这岂不是很麻烦?让我们让这些活动都永无止境地循环去吧:

[root@localhost ~]# cat note.sh
#!/bin/bash
while true
do
        read -p "Phone number: " phone
        now=`date "+%Y.%m.%d %H:%M:%S"`
        read -p "Name: " name
        read -p "Issue: " issue
        echo "$now/$phone/$name/$issue">>data.txt
done

我已经交换了read phonenow=date行的位置。这是因为我想要在输入电话号码后再获得时间。如果我把它放在循环的首行,那么循环一次后,变量 now 就会在数据存储到文件中后马上获得时间。而这并不好,因为下一次呼叫可能在20分钟后,甚至更晚。

[root@localhost ~]# ./note.sh
Phone number: 123
Name: Jim
Issue: Script still not works.
Phone number: 777
Name: Daniel
Issue: I broke my monitor
Phone number: ^C
[root@localhost ~]# cat data.txt
2015.04.23 21:38:56/123/Jim/Script hanging.
2015.04.23 21:43:50/321/Susane/Mouse was stolen
2015.04.23 21:47:55/123/Jim/Script still not works.
2015.04.23 21:48:16/777/Daniel/I broke my monitor
[root@localhost ~]#

注意: 要从无限循环中退出,你可以按[Ctrl]+[C]键。Shell会显示^表示 CTRL 键

使用管道重定向

让我们添加更多功能到我们的“弗兰肯斯坦(Frankenstein)”,我想要脚本在每次呼叫后显示某个统计数据。比如说,我想要查看各个号码呼叫了我几次。对于这个,我们应该cat文件data.txt:

[root@localhost ~]# cat data.txt
2015.04.23 21:38:56/123/Jim/Script hanging.
2015.04.23 21:43:50/321/Susane/Mouse was stolen
2015.04.23 21:47:55/123/Jim/Script still not works.
2015.04.23 21:48:16/777/Daniel/I broke my monitor
2015.04.23 22:02:14/123/Jimmy/New script also not working!!!
[root@localhost ~]#

现在,所有输出我们都可以重定向到cut命令,让cut来把每行切成一块一块(我们使用分隔符“/”),然后打印第二个字段:

[root@localhost ~]# cat data.txt | cut -d"/" -f2
123
321
123
777
123
[root@localhost ~]#

现在,我们可以把这个输出重定向打另外一个命令sort

[root@localhost ~]# cat data.txt | cut -d"/" -f2|sort
123
123
123
321
777
[root@localhost ~]#

然后只留下唯一的行。要统计唯一条目,只需添加-c键到uniq命令:

[root@localhost ~]# cat data.txt | cut -d"/" -f2 | sort | uniq -c
    3 123
    1 321
    1 777
[root@localhost ~]#

只要把这个添加到我们的循环的最后:

#!/bin/bash
while true
do
        read -p "Phone number: " phone
        now=`date "+%Y.%m.%d %H:%M:%S"`
        read -p "Name: " name
        read -p "Issue: " issue
        echo "$now/$phone/$name/$issue">>data.txt
        echo "===== We got calls from ====="
        cat data.txt | cut -d"/" -f2 | sort | uniq -c
        echo "--------------------------------"
done

运行:

[root@localhost ~]# ./note.sh
Phone number: 454
Name: Malini
Issue: Windows license expired.
===== We got calls from =====
    3 123
    1 321
    1 454
    1 777
--------------------------------
Phone number: ^C

当前场景贯穿了几个熟知的步骤:

  • 显示消息
  • 获取用户输入
  • 存储值到文件
  • 处理存储的数据

但是,如果用户有点责任心,他有时候需要输入数据,有时候需要统计,或者可能要在存储的数据中查找一些东西呢?对于这些事情,我们需要使用switches/cases,并知道怎样来很好地格式化输出。这对于在shell中“画”表格的时候很有用。


via: http://linoxide.com/linux-shell-script/guide-start-learning-shell-scripting-scratch/

作者:Petras Liumparas 译者:GOLinux 校对:wxy

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

最精简Linux Live是一系列基于linux内核BusyBox的自动生成最精简 Live Linux 系统linux shell脚本。所有所需的源码已经自动地被下载,而且所有的生成操作已经完全被封装在脚本中了。

如果你想定制属于自己的最精简Linux Live 的ISO镜像文件,以下是你需要做的:

  • 下载区域获得最新的脚本。
  • 把脚本解压到文件夹下。
  • 确保所有的脚本都可执行(可以使用命令chmod +x *.sh 添加可执行属性)
  • 在开始生成过程之前,取决于你使用什么Linux系统,可能会有一个或者多个依赖库(也可能不需要,或者更多)需要处理.如果使用Ubuntu的话,以下命令应该足以生成所有需要的依赖库:

sudo apt-get install wget
sudo apt-get install make
sudo apt-get install gcc
sudo apt-get install bc
sudo apt-get install syslinux
sudo apt-get install genisoimage
  • 执行脚本 build\_minimal\_linux\_live.sh 然后等待完成。如果你已经处理好所有的生成依赖库,那么在一台现代计算机上全部的过程耗费时间应该不超过30分钟。如果由于某些原因生成失败,最可能的应该是没有处理好生成依赖库。一些用户反应到生成所必需的包将会处理所有ubuntu上未知的生成依赖问题。如果使用Linux Mint 而且正在尝试安装 g++ 包,如果你依然遇到很多问题,那么你可以尝试安装必需包。在Fedora系统上你可能需要安装 glibc-static包。

请记得所有的生成依赖库由于你使用的Linux操作系统和已经安装的软件不同将会发生很大的变化。

如果依然有问题,请检查失败信息然后在google上搜索一下。如果你找不到解决方案,你可以询问一些更有经验的linux专家(如果你认识的话),或者另外一个选择是联系我们。请确保在发送给我们之前,你已经提前研究过这个问题。

  • 当脚本完成它们的工作,你将会在执行脚本的同目录下发现一个新创建的minimal\_linux\_live.iso 文件。你可以烧录iso镜像到CD/DVD,通过Universal USB Installer工具安装到USB闪存设备,或者直接在PC的虚拟机上运行,例如VirtualBox

生成的ISO镜像文件包括使用默认选项编译的linux内核,使用默认选项编译的BusyBox和非常轻量级的initramfs。这意味着你不需要 另辟蹊径去获得windows支持,也不需要使用任何花哨的桌面环境。你所需要的只是一个支持BusyBox applets 的shell控制台,和……好吧。这些就够了。这就是为什么称它为 "最精简"。

最好的消息是不仅操作系统很小很简单,而且生成脚本也同样的小巧玲珑。你可以很轻松地从脚本学习,并且修改他们去包含更多内容(我是很鼓励你这样做).在你学习完这些基础以后你将会拥有所有所必须的工具和技能去创造你自己功能完善的linux操作系统!完全从零开始!是不是帅呆了!?:)

这篇教程提供了更详细的脚本内部结构以及全部的生成过程,我鼓励你们如果有时间的话就去通读这个文档。

下面你会看到一些屏幕截图,它们展示了当你启动你的新一代 Minimal Linux Live 操作系统时的环境情况。


via: http://minimal.linux-bg.org/

译者:lfzark 校对:Caroline

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

以前我总想知道如何为我的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中国 荣誉推出

首先致上每日问候。Linux的浩瀚无垠,使人总能每次都提交与众不同的内容。我们“The-Tecmint-Team”的工作是给我们的读者提供一些独特的内容,这些内容不仅对他们的职业生涯很有用,同时也让他们增长知识。在此,我们就尝试这么去做,至于能取得多大的成功,就由我们的读者朋友们来判断吧。

Questions on Shell Scripting

我们为各类用户提供了关于Shell脚本语言和面试问题的很多教程,可以访问以下链接去阅读这些文章。

在此,作为shell脚本的附加内容,在本文中我们将从面试的角度解读与Linux Shell相关的问题。

1. 在shell脚本成功执行前,如何中断脚本执行?

解答:我们需要使用‘exit’命令来实现以上描述的情境。‘exit’命令被强制输出非0值时,脚本会报错并退出。在Unix环境下的shell脚本中,0值表示成功执行。因此,在脚本终止前执行一个不带引号的‘exit -1’命令将使脚本中止。

例如,创建以下一个名为“anything.sh”的脚本。

#!/bin/bash
echo "Hello"
exit -1
echo "bye"

保存文件并执行。

# sh anything.sh

Hello
exit.sh: 3: exit: Illegal number: -1

从上面的脚本中可以清楚地看到,在exit -1命令前,脚本执行得很好。

2. 如何使用Linux命令来移除文件头?

解答:当我们需要删除文件中的指定行时,‘sed’命令可以用来解决该问题。

这个是用来删除文件头(文件的首行)的正确命令。

# sed '1 d' file.txt

上面命令的问题是,它会在标准输出设备上输出不带首行的文件内容。为了保存输出到文件,我们需要使用重定向操作符,它将帮助你将输出重定向到文件。

# sed '1 d' file.txt > new_file.txt

好吧,其实sed命令内建的‘-i’开关就可以干这活,就不需要重定向符了吧。

# sed -i '1 d' file.txt

3. 你怎么检查一个文本文件中某一行的长度?

解答:‘sed’命令也可以用来查找文本文件中的某一行或者检查其长度。

sed -n ‘n p’ file.txt’可以解决,这里‘n’表示行号,‘p’打印出匹配内容(到标准输出),该命令通常与-n命令行选项连用。那么,怎样来获取长度计数呢?很明显,我们需要通过管道输出给‘wc’命令来计算。

# sed –n 'n p' file.txt | wc –c

要得到文本文件‘tecmint.txt’的第五行的长度,运行如下命令:

# sed -n '5 p' tecmint.txt | wc -c

4. 可以在Linux系统上查看到所有非打印字符吗?你是怎么做到的?

解答:可以。可以在Linux中查看所有的非打印字符。要实现上面所讲的方案,我们需要‘vi’编辑器的帮助。 怎样在‘vi’编辑器中显示非打印字符?
  • 打开vi编辑器。
  • 先按[esc]键,然后按‘:’进入到vi编辑器的命令模式。
  • 最后,从‘vi’编辑器的命令界面输入set list命令并执行。

: 这种方式可以查看文本文件中的所有非打印字符,包括ctrl+m(^M)

5. 假如你是一个员工组的团队领导,为xyz公司工作。公司要求你创建一个‘dir\_xyz’目录,让该组成员都能在该目录下创建或访问文件,但是除了文件创建者之外的其他人不能删除文件,你会怎么做?

解答:这真是个有趣的工作方案。好吧,上面所讲的方案,我们需要通过下面的步骤来实施,这简直就是小菜一碟。
# mkdir dir_xyz
# chmod g+wx dir_xyz
# chmod +t dir_xyz

第一行命令创建了一个目录(dir\_xyz),上面的第二行命令让组(g)具有‘写’和‘执行’的权限,而上面的最后一行命令——权限位最后的‘+t’是‘粘滞位’,它用来替换‘x’,表明在这个目录中,文件只能被它们的拥有者、目录的拥有者或者是超级用户root删除。

6. 你能告诉我一个Linux进程经历的各个阶段吗?

解答:一个Linux进程在它的一生中,通常经历了四个主要阶段。

这里是Linux进程要经历的四个阶段。

  • 等待:Linux进程等待资源。
  • 运行:Linux进程当前正在执行中。
  • 停止:Linux进程在成功执行后或收到杀死进程信号后停止。
  • 僵尸:如果该进程已经结束,但仍然留在进程表中,被称为‘僵尸’。

7. Linux中cut命令怎么用?

解答:‘cut’是一个很有用的Linux命令,当我们要截取文件的指定部分并打印到标准输出,当文本区域以及文件本身很大时,这个命令很有用。

例如,截取‘txt\_tecmint’文件的前10列。

# cut -c1-10 txt_tecmint

要截取该文件中的第二,第五和第七列。

# cut -d;-f2 -f5 -f7 txt_tecmint

8. ‘cmp’和‘diff’命令的区别是什么?

解答:‘cmp’和‘diff’命令用来获取相同的东西,但各有侧重。

diff’命令输出为了使两个文件一样而应该做的修改。而‘cmp’命令则将两个文件逐字节对比,并报告第一个不匹配的项。

9. 可以用‘echo’命令来替换‘ls’命令吗?

解答:可以的。‘ls’命令可以用‘echo’命令来替代。‘ls’命令列出目录内容,从替换上述命令的角度讲,我们可以使用‘echo *’,两个命令的输出完全一样。

10. 你可能听过inode吧。你能简要描述一下inode吗?

解答:‘inode’是一个‘数据结构’,在Linux上用于文件标识。每个文件在Unix系统上有一个独立的‘inode’和一个‘唯一的’inode号。

到此为止吧。在下一篇文章中,我们将讨论另外一些有趣味性而又有知识性的面试问题。到那时,别跑开,请上Tecmint.com,别忘了在下面的评论部分给我们提供一些有价值的反馈哦。


via: http://www.tecmint.com/interview-questions-on-shell-scripting/

译者:GOLinux 校对:wxy

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