[Translated]20150506 First Step Guide for Learning Shell Scripting.md

This commit is contained in:
GOLinux 2015-05-12 09:35:11 +08:00
parent b0b40b3b7b
commit a55240b2ea
2 changed files with 460 additions and 460 deletions

View File

@ -1,460 +0,0 @@
Translating by GOLinux!
First Step Guide for Learning Shell Scripting
================================================================================
![](http://blog.linoxide.com/wp-content/uploads/2015/04/myfirstshellscript.jpg)
Usually when people say "shell scripting" they have on mind bash, ksh, sh, ash or similar linux/unix scripting language. Scripting is another way to communicate with computer. Using graphic windows interface (not matter windows or linux) user can move mouse and clicking on the various objects like, buttons, lists, check boxes and so on. But it is very inconvenient way witch requires user participation and accuracy each time he would like to ask computer / server to do the same tasks (lets say to convert photos or download new movies, mp3 etc). To make all these things easy accessible and automated we could use shell scripts.
Some programming languages like pascal, foxpro, C, java needs to be compiled before they could be executed. They needs appropriate compiler to make our code to do some job.
Another programming languages like php, javascript, visualbasic do not needs compiler. So they need interpretersand we could run our program without compiling the code.
The shell scripts is also like interpreters, but it is usually used to call external compiled programs. Then captures the outputs, exit codes and act accordingly.
One of the most popular shell scripting language in the linux world is the bash. And i think (this is my own opinion) this is because bash shell allows user easily navigate through the history commands (previously executed) by default, in opposite ksh which requires some tuning in .profile or remember some "magic" key combination to walk through history and amend commands.
Ok, i think this is enough for introduction and i leaving for your judge which environment is most comfortable for you. Since now i will speak only about bash and scripting. In the following examples i will use the CentOS 6.6 and bash-4.1.2. Just make sure you have the same or greater version.
### Shell Script Streams ###
The shell scripting it is something similar to conversation of several persons. Just imagine that all command like the persons who able to do something if you properly ask them. Lets say you would like to write the document. First of all you need the paper, then you need to say the content to someone to write it, and finally you would like to store it somewhere. Or you would like build a house, so you will ask appropriate persons to cleanup the space. After they say "its done" then other engineers could build for you the walls. And finally, when engineers also tell "Its done" you can ask the painters to color your house. And what would happen if you ask the painters coloring your walls before they are built? I think they will start to complain. Almost all commands like the persons could speak and if they did its job without any issues they speaks to "standard output". If they can't to what you asking - they speaking to the "standard error". So finally all commands listening for you through "standard input".
Quick example- when you opening linux terminal and writing some text - you speaking to bash through "standard input". So ask the bash shell **who am i**
root@localhost ~]# who am i <--- you speaking through the standard input to bash shell
root pts/0 2015-04-22 20:17 (192.168.1.123) <--- bash shell answering to you through the standard output
Now lets ask something that bash will not understand us:
[root@localhost ~]# blablabla <--- and again, you speaking through standard input
-bash: blablabla: command not found <--- bash complaining through standard error
The first word before ":" usually is the command which complaining to you. Actually each of these streams has their own index number:
- standard input (**stdin**) - 0
- standard output (**stdout**) - 1
- standard error (**stderr**) - 2
If you really would like to know to witch output command said something - you need to redirect (to use "greater than ">" symbol after command and stream index) that speech to file:
[root@localhost ~]# blablabla 1> output.txt
-bash: blablabla: command not found
In this example we tried to redirect 1 (**stdout**) stream to file named output.txt. Lets look does to the content of that file. We use the command cat for that:
[root@localhost ~]# cat output.txt
[root@localhost ~]#
Seams that is empty. Ok now lets try to redirect 2 (**stderr**) streem:
[root@localhost ~]# blablabla 2> error.txt
[root@localhost ~]#
Ok, we see that complains gone. Lets chec the file:
[root@localhost ~]# cat error.txt
-bash: blablabla: command not found
[root@localhost ~]#
Exactly! We see that all complains was recorded to the errors.txt file.
Sometimes commands produces **stdout** and **stderr** simultaniously. To redirect them to separate files we can use the following syntax:
command 1>out.txt 2>err.txt
To shorten this syntax a bit we can skip the "1" as by default the **stdout** stream will be redirected:
command >out.txt 2>err.txt
Ok, lets try to do something "bad". lets remove the file1 and folder1 with the rm command:
[root@localhost ~]# rm -vf folder1 file1 > out.txt 2>err.txt
Now check our output files:
[root@localhost ~]# cat out.txt
removed `file1'
[root@localhost ~]# cat err.txt
rm: cannot remove `folder1': Is a directory
[root@localhost ~]#
As we see the streams was separated to different files. Sometimes it is not handy as usually we want to see the sequence when the errors appeared - before or after some actions. For that we can redirect both streams to the same file:
command >>out_err.txt 2>>out_err.txt
Note : Please notice that i use ">>" instead of ">". It allows us to append file instead of overwrite.
We can redirect one stream to another:
command >out_err.txt 2>&1
Let me explain. All stdout of the command will be redirected to the out_err.txt. The errout will be redirected to the 1-st stream which (as i already explained above) will be redirected to the same file. Let see the example:
[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 ~]#
Looking at the combined output we can state that first of all **rm** command tried to remove the folder2 and it was not success as linux require the **-r** key for **rm** command to allow remove folders. At the second the file2 was removed. By providing the **-v** (verbose) key for the **rm** command we asking rm command to inform as about each removed file or folder.
This is almost all you need to know about redirection. I say almost, because there is one more very important redirection which called "piping". By using | (pipe) symbol we usually redirecting **stdout** streem.
Lets say we have the text file:
[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!
and we need to find the lines in it with the words "Hello". Linux has the **grep** command for that:
[root@localhost ~]# grep Hello text_file.txt
This lilne contains Hello
This also containd Hello
Hello bash world!
[root@localhost ~]#
This is ok when we have file and would like to sech in it. But what to do if we need to find something in the output of another command? Yes, of course we can redirect the output to the file and then look in it:
[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 ~]#
If you going to grep something with white spaces embrace that with " quotes!
Note : fdisk command shows information about Linux OS disk drives
As we see this way is not very handy as soon we will mess the space with temporary files. For that we can use the pipes. They allow us redirect one command **stdout** to another command **stdin** streams:
[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 ~]#
As we see, we get the same result without any temporary files. We have redirected **frisk stdout** to the **grep stdin**.
**Note** : Pipe redirection is always from left to right.
There are several other redirections but we will speak about them later.
### Displaying custom messages in the shell ###
As we already know usually communication with and within shell is going as dialog. So lets create some real script which also will speak with us. It will allow you to learn some simple commands and better understand the scripting concept.
Imagine we are working in some company as help desk manager and we would like to create some shell script to register the call information: phone number, User name and brief description about issue. We going to store it in the plain text file data.txt for future statistics. Script it self should work in dialog way to make live easy for help desk workers. So first of all we need to display the questions. For displaying any messages there is echo and printf commands. Both of them displaying messages, but printf is more powerful as we can nicely form output to align it to the right, left or leave dedicated space for message. Lets start from simple one. For file creation please use your favorite text editor (kate, nano, vi, ...) and create the file named note.sh with the command inside:
echo "Phone number ?"
### Script execution ###
After you have saved the file we can run it with bash command by providing our file as an argument:
[root@localhost ~]# bash note.sh
Phone number ?
Actually to use this way for script execution is not handy. It would be more comfortable just execute the script without any **bash** command as a prefix. To make it executable we can use **chmod** command:
[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 ~]#
![set permission script file](http://blog.linoxide.com/wp-content/uploads/2015/04/Capture.png)
**Note** : ls command displays the files in the current folder. By adding the keys -la it will display a bit more information about files.
As we see, before **chmod** command execution, script has only read (r) and write (w) permissions. After **chmod +x** it got execute (x) permissions. (More details about permissions i am going to describe in next article.) Now we can simply run it:
[root@localhost ~]# ./note.sh
Phone number ?
Before script name i have added ./ combination. . (dot) in the unix world means current position (current folder), the / (slash) is the folder separator. (In Windows OS we use \ (backslash) for the same). So whole this combination means: "from the current folder execute the note.sh script". I think it will be more clear for you if i run this script with full path:
[root@localhost ~]# /root/note.sh
Phone number ?
[root@localhost ~]#
It also works.
Everything would be ok if all linux users would have the same default shell. If we simply execute this script default user shell will be used to parse script content and run the commands. Different shells have a bit different syntax, internal commands, etc. So to guarantee the **bash** will be used for our script we should add **#!/bin/bash** as the first line. In this way default user shell will call **/bin/bash** and only then will execute following shell commands in the script:
[root@localhost ~]# cat note.sh
#!/bin/bash
echo "Phone number ?"
Only now we will be 100% sure that **bash** will be used to parse our script content. Lets move on.
### Reading the inputs ###
After we have displayed the message script should wait for answer from user. There is the command **read**:
#!/bin/bash
echo "Phone number ?"
read phone
After execution script will wait for the user input until he press the [ENTER] key:
[root@localhost ~]# ./note.sh
Phone number ?
12345 <--- here is my input
[root@localhost ~]#
Everything you have input will be stored to the variable **phone**. To display the value of variable we can use the same **echo** command:
[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 ~]#
In **bash** shell we using **$** (dollar) sign as variable indication, except when reading into variable and few other moments (will describe later).
Ok, now we are ready to add the rest questions:
#!/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 ~]#
### Using stream redirection ###
Perfect! There is left to redirect everything to the file data.txt. As a field separator we going to use / (slash) symbol.
**Note** : You can chose any which you think is the best, bat be sure that content will not have thes symbols inside. It will cause extra fields in the line.
Do not forget to use ">>" instead of ">" as we would like to append the output to the end of file!
[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 ~]#
**Note** : The command **tail** displays the last **-n** lines of the file.
Bingo. Lets run once again:
[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 ~]#
Our file is growing. Lets add the date in the front of each line. This will be useful later when playing with data while calculating statistic. For that we can use command date and give it some format as i do not like default one:
[root@localhost ~]# date
Thu Apr 23 21:33:14 EEST 2015 <---- default output of dta command
[root@localhost ~]# date "+%Y.%m.%d %H:%M:%S"
2015.04.23 21:33:18 <---- formated output
There are several ways to read the command output to the variable. In this simple situation we will use ` (back quotes):
[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 ~]#
Hmmm... Our script looks a bit ugly. Lets prettify it a bit. If you would read manual about **read** command you would find that read command also could display some messages. For this we should use -p key and message:
[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
You can fine a lots of interesting about each command directly from the console. Just type: **man read, man echo, man date, man ....**
Agree it looks much better!
[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 ~]#
And the cursor is right after the message (not in new line) what makes a bit sense.
Loop
Time to improve our script. If user works all day with the calls it is not very handy to run it each time. Lets add all these actions in the never-ending loop:
[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
I have swapped **read phone** and **now=`date** lines. This is because i would like to get the time right after the phone number will be entered. If i would left it as the first line in the loop **- the** now variable will get the time right after the data was stored in the file. And it is not good as the next call could be after 20 mins or so.
[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 ~]#
NOTE: To exit from the never-ending loop you can by pressing [Ctrl]+[C] keys. Shell will display ^ as the Ctrl key.
### Using pipe redirection ###
Lets add more functionality to our "Frankenstein" I would like the script will display some statistic after each call. Lets say we want to see the how many times each number called us. For that we should cat the data.txt file:
[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 ~]#
Now all this output we can redirect to the **cut** command to **cut** each line into the chunks (our delimiter "/") and print the second field:
[root@localhost ~]# cat data.txt | cut -d"/" -f2
123
321
123
777
123
[root@localhost ~]#
Now this output we can redirect to another command to **sort**:
[root@localhost ~]# cat data.txt | cut -d"/" -f2|sort
123
123
123
321
777
[root@localhost ~]#
and leave only unique lines. To count unique entries just add **-c** key for **uniq** command:
[root@localhost ~]# cat data.txt | cut -d"/" -f2 | sort | uniq -c
3 123
1 321
1 777
[root@localhost ~]#
Just add this to end of our loop:
#!/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
Run it:
[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
![running script](http://blog.linoxide.com/wp-content/uploads/2015/04/Capture11.png)
Current scenario is going through well-known steps like:
- Display message
- Get user input
- Store values to the file
- Do something with stored data
But what if user has several responsibilities and he needs sometimes to input data, sometimes to do statistic calculations, or might be to find something in stored data? For that we need to implement switches / cases. In next article i will show you how to use them and how to nicely form the output. It is useful while "drawing" the tables in the shell.
--------------------------------------------------------------------------------
via: http://linoxide.com/linux-shell-script/guide-start-learning-shell-scripting-scratch/
作者:[Petras Liumparas][a]
译者:[译者ID](https://github.com/译者ID)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](https://linux.cn/) 荣誉推出
[a]:http://linoxide.com/author/petrasl/

View File

@ -0,0 +1,460 @@
Shell脚本学习初次操作指南
================================================================================
![](http://blog.linoxide.com/wp-content/uploads/2015/04/myfirstshellscript.jpg)
通常当人们提到“shell脚本语言”时浮现在他们脑海中是bashkshsh或者其它相类似的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 <--- you speaking through the standard input to bash shell
root pts/0 2015-04-22 20:17 (192.168.1.123) <--- bash shell answering to you through the standard output
现在让我们说一些bash听不懂的问题
[root@localhost ~]# blablabla <--- 你又在和标准输入说话了
-bash: blablabla: command not found <--- bash通过标准错误在发牢骚了
“:”之前的第一个单词通常是向你发牢骚的命令。实际上,这些流中的每一个都有它们自己的索引号:
- 标准输入(**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文件里头去了。
有时候,命令会同时产生**stdout**和**stderr**。要重定向它们到不同的文件,我们可以使用以下语句:
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-st流上面已经解释过了而该流会被重定向到同一个文件。让我们看这个实例
[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更为强大因为我们可以通过它很好地格式化输出我们可以让它右对齐、左对齐或者为信息留出专门的空间。让我们从一个简单的例子开始吧。要创建文件请使用你喜欢的文本编辑器katenanovi……然后创建名为note.sh的文件里面写入这些命令
echo "Phone number ?"
### Script执行 ###
在保存文件后我们可以使用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 ~]#
![set permission script file](http://blog.linoxide.com/wp-content/uploads/2015/04/Capture.png)
**注意** 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 ~]#
光标在消息的后面(不是在新的一行中),这有点意思。
循环
是时候来改进我们的脚本了。如果用户一整天都在接电话,如果每次都要去运行,这岂不是很麻烦?让我们让这些活动都永无止境地循环去吧:
[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 phone**和**now=`date`**行。这是因为我想要在输入电话号码后再获得时间。如果我把它放在循环**- the**的首行变量就会在数据存储到文件中后获得时间。而这并不好因为下一次呼叫可能在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键。
### 使用管道重定向 ###
让我们添加更多功能到我们的“弗兰肯斯坦”我想要脚本在每次呼叫后显示某个统计数据。比如说我想要查看各个号码呼叫了我几次。对于这个我们应该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
![running script](http://blog.linoxide.com/wp-content/uploads/2015/04/Capture11.png)
当前场景贯穿了几个熟知的步骤:
- 显示消息
- 获取用户输入
- 存储值到文件
- 处理存储的数据
但是如果用户有点责任心他有时候需要输入数据有时候需要统计或者可能要在存储的数据中查找一些东西呢对于这些事情我们需要使用switches/cases并知道怎样来很好地格式化输出。这对于在shell中“画”表格的时候很有用。
--------------------------------------------------------------------------------
via: http://linoxide.com/linux-shell-script/guide-start-learning-shell-scripting-scratch/
作者:[Petras Liumparas][a]
译者:[GOLinux](https://github.com/GOLinux)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创翻译,[Linux中国](https://linux.cn/) 荣誉推出
[a]:http://linoxide.com/author/petrasl/