本文為Linux Shell Scripting Tutorial (LSST) v2.0學習記錄
第二章:開始shell腳本編程
本章節(jié)學習目標:
編寫你第一個shell 程序
理解創(chuàng)建一個shell腳本的步驟
2.1 Bash shell(全稱Bourne again shell)
有關bash的創(chuàng)建歷史(來自維基百科):
Bourne shell是一個交互式的shell誓竿,由AT&T實驗室的史蒂夫在1977年發(fā)布磅网,位于大多數(shù)Unix系統(tǒng)上的/bin/sh,隨著時間的發(fā)展筷屡,GNU計劃的誕生伴隨著shell的開發(fā)涧偷,這個時候1987年布萊恩編寫了Bash,也就是Bourne again shell毙死,總的來說燎潮,Bash雖然是一個滿足POSIX規(guī)范的shell,但有很多拓展扼倘。
Bash就是shell确封,或者可以說是Linux系統(tǒng)的命令語言解釋器:
- Bash式GNU計劃的產(chǎn)物
- Linux的默認shell
- 反向兼容UNIX系統(tǒng)的sh
- 與Korn shell(ksh)以及C shell(csh)兼容
- 在多平臺類似UNIX/dos/Windows都是可運行多個
BASH的提升特性
- 命令行編輯
- 命令行補全
- 無限大小的命令歷史
- 提示控制
- 不限大小的array
- 以2-64為基數(shù)的整數(shù)運算
- ……
作者
- Brian J. Fox authored the GNU Bash shell, in 1987.
- Fox maintained Bash as the primary maintainer until 1993, at which point Chet Ramey took over.
- Chet Ramey is the current maintainer of the GNU Bourne Again Shell and GNU Readline.
下載
- bash式Linux的默認shell,最新版本可以在official website下載
- Bash home page
- Chet's home page
2.2 shell命令
bash shell有兩種類型的命令:
- 內置命令
- 外部命令(在bin/目錄里面的命令)
bash和命令類型
bash接受以下幾種類型的命令:
- 別名(例如
ll
) - 關鍵詞(例如
if
) - 函數(shù)(例如
genpassword
) - 內置命令(例如
pwd
) - 文件(例如/bin/date)
type命令
找出某個命令(ls)是內置命令還是外部命令:
new@Chevy-PC:~$ type ls
ls is aliased to `ls --color=auto'
# 顯示為外部命令
找出某個命令(command)是內置命令還是外部命令:
new@Chevy-PC:~$ type -a history
history is a shell builtin
# 顯示為內置命令
bash命令關鍵詞以及內置命令
- JOB_SPEC &
- (( expression ))
- . filename
- [[:]]
- [ arg... ]
- expression
- alias
- bg
- bind
- builtin
- caller
- case
- command
- compgen
- complete
- continue
- declare
- dirs
- disown
- echo
- enable
- eval
- exec
- exit
- export
- false
- fc
- fg
- for
- getopts
- hash
- help
- history
- if
- jobs
- kill
- let
- local
- logout
- popd
- printf
- pushd
- pwd
- read
- readonly
- return
- select
- set
- shift
- shopt
- source
- suspend
- test
- time
- times
- trap
- true
- type
- typeset
- ulimit
- umask
- unalias
- unset
- until
- variables
- while
2.3 Hello, World! 第一個shell腳本
創(chuàng)建一個shell腳本你需要經(jīng)過以下幾個步驟:
- 利用一個文本編輯器(例如vi)再菊,將你的指令寫入一個文本
- 保存該文本并退出
- 改變該文本的可執(zhí)行權限
- 測試該腳本并放入你的生產(chǎn)環(huán)境
- 最簡單的shell腳本就是一行代碼告訴計算機執(zhí)行一個命令
讓我們來熟悉vi的操作并創(chuàng)建第一個shell script
# 打開一個文件
vi first_script.sh
# 進入編輯模式
# 按Esc鍵爪喘,然后按I鍵就進入編輯模式
# 進入命令模式
# 按Esc鍵就進入命令模式
# 保存文件
# 按Esc鍵然后輸入:w
# 或者按Esc鍵然后輸入:w first_script.sh
# 保存并退出文件
# 按Esc鍵然后輸入:wq
# 或者按Esc鍵然后輸入:x
# 跳到某一行
# 按Esc鍵然后輸入:x, x代表第幾行
# 搜索一個字符
# 按Esc鍵然后輸入/str, str代表該字符
# 退出vi(不保存)
# 按Esc鍵然后輸入:q
# 讓我們來編輯第一個腳本,輸入以下字符并保存退出
#!/bin/bash
echo "Hello, World!"
echo "Knowledge is power."
# 將該文件改為可以執(zhí)行文件并執(zhí)行
new@Chevy-PC:~$ chmod 777 first_script.sh
new@Chevy-PC:~$ ./first_script.sh
Hello, World!
Knowledge is power.
Shebang
在腳本的第一行纠拔,我們需要告訴系統(tǒng)用哪種程序來運行這個腳本腥放,寫法為#!/path_to_binaries
,我們一般稱其為Shebang或者bang行[1]
在bash腳本里面第一行我們一般寫成#!/bin/bash
绿语,有些程序使用perl
來運行的話秃症,就寫成#!/bin/perl
一個#!/bin.sh
的腳本示例:
etc/init.d/policykit
#! /bin/sh
### BEGIN INIT INFO
# Provides: policykit
# Required-Start: $local_fs
# Required-Stop: $local_fs
# Default-Start: 2 3 4 5
# Default-Stop:
# Short-Description: Create PolicyKit runtime directories
# Description: Create directories which PolicyKit needs at runtime,
# such as /var/run/PolicyKit
### END INIT INFO
# Author: Martin Pitt <martin.pitt@ubuntu.com>
case "$1" in
start)
mkdir -p /var/run/PolicyKit
chown root:polkituser /var/run/PolicyKit
chmod 770 /var/run/PolicyKit
;;
stop|restart|force-reload)
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
exit 3
;;
esac
:
Shell注釋
在腳本里面#
號后面的代碼會被忽略,這行代碼我們成為注釋吕粹,注釋可以幫助我們理解代碼并且便于修改:
#!/bin/bash
# A Simple Shell Script To Get Linux Network Information
# Vivek Gite - 30/Aug/2009
echo "Current date : $(date) @ $(hostname)"
echo "Network configuration"
/sbin/ifconfig
多行注釋可以使用HERE DOCUMENT 特性來進行注釋:
#!/bin/bash
echo "Adding new users to LDAP Server..."
<<COMMENT1
Master LDAP server : dir1.nixcraft.net.in
Add user to master and it will get sync to backup server too
Profile and active directory hooks are below
COMMENT1
echo "Searching for user..."
設置腳本權限
一個剛編輯完的腳本無法直接執(zhí)行种柑,你需要使用chmod
命令給與其可執(zhí)行權限或者直接調用對應的命令來執(zhí)行,下面的操作具有同樣的效果:
chmod +x script.sh && ./script.sh
chmod 777 script.sh && ./script.sh
sh script.sh
使用ls -l
可以查看一個文件的權限:
new@Chevy-PC:~$ ls -l first.sh
-rwxrwxrwx 1 new new 61 Jul 15 14:24 first.sh
更多chomd
的操作可以使用man chmod
查看
為什么不直接使用
scriptname
來調用腳本匹耕?為什么當工作目錄$PWD
正好是scriptname
所在目錄時也不起作用聚请?因為一些安全原因,當前目錄./
并不會被默認添加到用戶的$PATH路徑中稳其。因此需要用戶顯式使用 ./scriptname 在當前目錄下調用腳本驶赏。
腳本debug
在運行腳本的時候需要加上-x
參數(shù)或者-xv
參數(shù),又或者是將shebang行改成#!/bin.bash-x
使用內置命令
bash shell提供了debug選項既鞠,可以使用set
命令打開或者關閉:
-
set -x
:當腳本被執(zhí)行的時候展示命令及參數(shù) -
set -v
:當腳本被讀入的時候展示輸入行 -
set -n
:讀入命令但不執(zhí)行(在檢查腳本是否有變量名重合的時候)
#!/bin/bash
### Turn on debug mode ###
set -x
# Run shell commands
echo "Hello $(LOGNAME)"
echo "Today is $(date)"
echo "Users currently on the machine, and their processes:"
### Turn OFF debug mode ###
set +x
# Add more commands without debug mode
#!/bin/bash
set -n # only read command but do not execute them
set -o noexec
echo "This is a test"
# no file is created as bash will only read commands but do not executes them
本章節(jié)復習題
練習題1
按照下面代碼寫一個腳本并運行煤傍,觀察輸出:
# Script to print currently logged in users information, and current date & time.
clear
echo "Hello $USER"
echo -e "Today is \c ";date
echo -e "Number of user login : \c" ; who | wc -l
echo "Calendar"
cal
exit 0
? 輸出結果:
Hello new
Today is Tue Jul 16 15:34:58 CST 2019
Number of user login : 0
Calendar
July 2019
Su Mo Tu We Th Fr Sa
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
練習題2
寫一個程序,打印出你最喜歡的電影名稱嘱蛋,同時在下一行打印出導演的名稱(以下是我自己的答案):
#!/bin/bash
# print moive and director
set -e
my_favorite_moive="gone_with_the_wind"
moive_director="not_known"
echo -e "my_favorite_moive is \c";
echo $my_favorite_moive
echo -e "the moive's director is \c";
echo $moive_director
exit 0
練習題3
寫一個shell腳本蚯姆,打印你的名字五续,在用戶按<ENTER>鍵之前保持等待狀態(tài):
#!/bin/bash
echo "Vivek Gite"
read -p "Press [Enter] key to continue..." fakeEnterKey
練習題4
列出十個內置命令和外部命令:
# builtin commands
history
break
cd
continue
eval
exit
grep
kill
# external commands
ls
gzip
練習題5
使用cd
命令進入/etc/init.d目錄下,查看各種系統(tǒng)啟動腳本:
new@Chevy-PC:/bin$ cd /etc/init.d/
new@Chevy-PC:/etc/init.d$ ll
total 156
-rwxr-xr-x 1 root root 2269 Apr 22 2017 acpid*
-rwxr-xr-x 1 root root 4335 Mar 23 2018 apparmor*
-rwxr-xr-x 1 root root 2802 Nov 21 2017 apport*
-rwxr-xr-x 1 root root 1071 Aug 22 2015 atd*
-rwxr-xr-x 1 root root 1232 Apr 19 2018 console-setup.sh*
-rwxr-xr-x 1 root root 3049 Nov 16 2017 cron*
-rwxr-xr-x 1 root root 937 Mar 18 2018 cryptdisks*
-rwxr-xr-x 1 root root 978 Mar 18 2018 cryptdisks-early*
-rwxr-xr-x 1 root root 2813 Nov 16 2017 dbus*
-rwxr-xr-x 1 root root 4489 Jun 29 2018 ebtables*
-rwxr-xr-x 1 root root 3809 Feb 15 2018 hwclock.sh*
-rwxr-xr-x 1 root root 2444 Oct 25 2017 irqbalance*
-rwxr-xr-x 1 root root 1503 Feb 22 2018 iscsid*
-rwxr-xr-x 1 root root 1479 Feb 16 2018 keyboard-setup.sh*
-rwxr-xr-x 1 root root 2044 Aug 16 2017 kmod*
-rwxr-xr-x 1 root root 695 Dec 3 2017 lvm2*
-rwxr-xr-x 1 root root 571 Dec 3 2017 lvm2-lvmetad*
-rwxr-xr-x 1 root root 586 Dec 3 2017 lvm2-lvmpolld*
-rwxr-xr-x 1 root root 2378 Jun 17 2018 lxcfs*
-rwxr-xr-x 1 root root 2240 Jun 6 2018 lxd*
-rwxr-xr-x 1 root root 2653 Jun 26 2018 mdadm*
-rwxr-xr-x 1 root root 1249 Jun 26 2018 mdadm-waitidle*
-rwxr-xr-x 1 root root 2503 Feb 22 2018 open-iscsi*
-rwxr-xr-x 1 root root 1846 Mar 22 2018 open-vm-tools*
-rwxr-xr-x 1 root root 1366 Jan 17 2018 plymouth*
-rwxr-xr-x 1 root root 752 Jan 17 2018 plymouth-log*
-rwxr-xr-x 1 root root 1191 Jan 18 2018 procps*
-rwxr-xr-x 1 root root 4355 Dec 13 2017 rsync*
-rwxr-xr-x 1 root root 2864 Jan 15 2018 rsyslog*
-rwxr-xr-x 1 root root 1222 May 22 2017 screen-cleanup*
-rwxr-xr-x 1 root root 3837 Jan 26 2018 ssh*
-rwxr-xr-x 1 root root 5974 Apr 21 2018 udev*
-rwxr-xr-x 1 root root 2083 Aug 16 2017 ufw*
-rwxr-xr-x 1 root root 1391 Jul 18 2018 unattended-upgrades*
-rwxr-xr-x 1 root root 1306 May 16 2018 uuidd*