shell script
时刻记住这是 一个脚本语言,一个更加彻底的解释型语言
Introduction
命令行最前面的例如git@22373058
修改
PS1="$ "; export PS1
Hello World脚本程序
$ echo '#!/bin/sh' > my-script.sh
$ echo 'echo Hello World' >> my-script.sh
$ chmod 755 my-script.sh
$ ./my-script.sh
Hello World
Philosophy
powerful echo!!
#!/bin/sh
# This is a comment!
echo "Hello World" # This is a comment, too!
echo "Hello World"
echo "Hello * World"
echo Hello * World
echo Hello World
echo "Hello" World
echo Hello " " World
echo "Hello "*" World"
echo `hello` world
echo 'hello' world
Variables
#!/bin/sh
MY_MESSAGE="Hello World"
echo $MY_MESSAGE
Note that there must be no spaces around the “=
” sign: VAR=value
works; VAR = value
doesn’t work. In the first case, the shell sees the “=
” symbol and treats the command as a variable assignment. In the second case, the shell assumes that VAR must be the name of a command and tries to execute it.
这里必须要加引号,这是因为一个var只能有一个值
$ x=6
$ expr $x+1
6+1
$ expr $x + 1
7
program expr
only expects numbers.因此这里的必须是数字
We can interactively set variable names using the read
command.
#!/bin/sh
echo What is your name?
read MY_NAME
echo "Hello $MY_NAME - hope you're well."
变量的作用域
-
export
-
source 可以使用
.
来进行source$ cat myvar2.sh #!/bin/bash echo "MYVAR is $MYVAR" MYVAR="hi there" echo "MYVAR is $MYVAR" $ . ./myvar2.sh MYVAR is hi there MYVAR is hi there
Loops
For Loops
#!/bin/sh
for i in 1 2 3 4 5
do
echo "Looping ... number $i"
done
While Loops
#!/bin/sh
INPUT_STRING=hello
while [ "$INPUT_STRING" != "bye" ]
do
echo "Please type something in (bye to quit)"
read INPUT_STRING
echo "You typed: $INPUT_STRING"
done
注意[ ]
和中间的语句都要有空格隔开,因为需要把[
理解为命令 ]
理解为参数,所以必须要加空格
#!/bin/bash
while :
do
echo "please type something (^C to quit)"
read INPUT_STRING
echo "you typed : $INPUT_STRING"
done
注意while
后面就必须要有空格了
#!/bin/sh
while read input_text
do
case $input_text in
hello) echo English ;;
howdy) echo American ;;
gday) echo Australian ;;
bonjour) echo French ;;
"guten tag") echo German ;;
*) echo Unknown Language: $input_text
;;
esac
done < myfile.txt
这个是从myfile.txt
作为输入流进行处理
A handy Bash (but not Bourne Shell) tip I learned recently from the Linux From Scratch project is:
mkdir rc{0,1,2,3,4,5,6,S}.d
instead of the more cumbersome:
for runlevel in 0 1 2 3 4 5 6 S
do
mkdir rc${runlevel}.d
done
And this can be done recursively, too:
$ cd /
$ ls -ld {,usr,usr/local}/{bin,sbin,lib}
drwxr-xr-x 2 root root 4096 Oct 26 01:00 /bin
drwxr-xr-x 6 root root 4096 Jan 16 17:09 /lib
drwxr-xr-x 2 root root 4096 Oct 27 00:02 /sbin
drwxr-xr-x 2 root root 40960 Jan 16 19:35 usr/bin
drwxr-xr-x 83 root root 49152 Jan 16 17:23 usr/lib
drwxr-xr-x 2 root root 4096 Jan 16 22:22 usr/local/bin
drwxr-xr-x 3 root root 4096 Jan 16 19:17 usr/local/lib
drwxr-xr-x 2 root root 4096 Dec 28 00:44 usr/local/sbin
drwxr-xr-x 2 root root 8192 Dec 27 02:10 usr/sbin
Test
$ type [
[ 是 shell 内建
$ which [
/usr/bin/[
$ ls -l /usr/bin/[
-rwxr-xr-x 1 root root 51632 2月 8 2022 '/usr/bin/['
$ ls -l /usr/bin/test
-rwxr-xr-x 1 root root 43440 2月 8 2022 /usr/bin/test
This means that ‘[
’ is actually a program, just like ls
and other programs, so it must be surrounded by spaces:
if [$foo = "bar" ]
will not work; it is interpreted as if test$foo = "bar" ]
, which is a ‘]
’ without a beginning ‘[
’. Put spaces around all your operators.
关于条件表达式
Some shells also accept “==
” for string comparison; this is not portable, a single “=
” should be used for strings, or “-eq
” for integers.
The syntax for if...then...else...
is:
if [ ... ]
then
# if-code
else
# else-code
fi
Also, be aware of the syntax - the “if [ ... ]
” and the “then
” commands must be on different lines. Alternatively, the semicolon “;
” can separate them:
if [ ... ]; then
# do something
fi
You can also use the elif
, like this:
if [ something ]; then
echo "Something"
elif [ something_else ]; then
echo "Something else"
else
echo "None of the above"
fi
example
#!/bin/sh
if [ "$X" -lt "0" ]
then
echo "X is less than zero"
fi
if [ "$X" -gt "0" ]; then
echo "X is more than zero"
fi
[ "$X" -le "0" ] && \
echo "X is less than or equal to zero"
[ "$X" -ge "0" ] && \
echo "X is more than or equal to zero"
[ "$X" = "0" ] && \
echo "X is the string or number \"0\""
[ "$X" = "hello" ] && \
echo "X matches the string \"hello\""
[ "$X" != "hello" ] && \
echo "X is not the string \"hello\""
[ -n "$X" ] && \
echo "X is of nonzero length"
[ -f "$X" ] && \
echo "X is the path of a real file" || \
echo "No such file: $X"
[ -x "$X" ] && \
echo "X is the path of an executable file"
[ "$X" -nt "/etc/passwd" ] && \
echo "X is a file which is newer than /etc/passwd"
Note that we can use the semicolon (;
) to join two lines together. This is often done to save a bit of space in simple if
statements.
The backslash (\
) serves a similar, but opposite purpose: it tells the shell that this is not the end of the line, but that the following line should be treated as part of the current line. This is useful for readability. It is customary to indent the following line after a backslash (\
) or semicolon (;
).
#!/bin/sh
X=0
while [ -n "$X" ] # 表示X不为空
do
echo "Enter some text (RETURN to quit)"
read X
echo "You said: $X"
done
Note that running this script will end untidily:
$ ./test2.sh
Enter some text (RETURN to quit)
fred
You said: fred
Enter some text (RETURN to quit)
wilma
You said: wilma
Enter some text (RETURN to quit)
You said:
$
This can be tidied up with another test within the loop:
#!/bin/sh
X=0
while [ -n "$X" ]
do
echo "Enter some text (RETURN to quit)"
read X
if [ -n "$X" ]; then
echo "You said: $X"
fi
done
Case
#!/bin/sh
echo "Please talk to me ..."
while :
do
read INPUT_STRING
case $INPUT_STRING in
hello)
echo "Hello yourself!"
;;
bye)
echo "See you again!"
break
;;
*)
echo "Sorry, I don't understand"
;;
esac
done
echo
echo "That's all folks!"
Variables-Part2
The first set of variables we will look at are $0 .. $9
and $#
.
The variable $0
is the basename of the program as it was called.
$1 .. $9
are the first 9 additional parameters the script was called with.
The variable $@
is all parameters $1 .. whatever
. The variable $*
, is similar, but does not preserve any whitespace, and quoting, so “File with spaces” becomes “File” “with” “spaces”. This is similar to the echo
stuff we looked at in A First Script. As a general rule, use $@
and avoid $*
.
$#
is the number of parameters the script was called with.
#!/bin/sh
echo "I was called with $# parameters"
echo "My name is $0"
echo "My first parameter is $1"
echo "My second parameter is $2"
echo "All parameters are $@"
Another interesting variable is IFS
. This is the Internal Field Separator. The default value is SPACE TAB NEWLINE
, but if you are changing it, it’s easier to take a copy, as shown:
#!/bin/sh
old_IFS="$IFS"
IFS=:
echo "Please input some data separated by colons ..."
read x y z
IFS=$old_IFS
echo "x is $x y is $y z is $z"
This script runs like this:
$ ./ifs.sh
Please input some data separated by colons ...
hello:how are you:today
x is hello y is how are you z is today
Note that if you enter: “hello:how are you:today:my:friend” then the output would be:
$ ./ifs.sh
Please input some data separated by colons ...
hello:how are you:today:my:friend
x is hello y is how are you z is today:my:friend
Function
#!/bin/sh
# A simple script with a function...
add_a_user()
{
USER=$1
PASSWORD=$2
shift; shift;
# Having shifted twice, the rest is now comments ...
COMMENTS=$@
echo "Adding user $USER ..."
echo useradd -c "$COMMENTS" $USER
echo passwd $USER $PASSWORD
echo "Added user $USER ($COMMENTS) with pass $PASSWORD"
}
###
# Main body of script starts here
###
echo "Start of script..."
add_a_user bob letmein Bob Holness the presenter
add_a_user fred badpassword Fred Durst the singer
add_a_user bilko worsepassword Sgt. Bilko the role model
echo "End of script..."
Recursion
Functions can be recursive - here’s a simple example of a factorial function:
#!/bin/sh
factorial()
{
if [ "$1" -gt "1" ]; then
i=`expr $1 - 1`
j=`factorial $i`
k=`expr $1 \* $j`
echo $k
else
echo 1
fi
}
while :
do
echo "Enter a number:"
read x
factorial $x
done
As promised, we will now briefly discuss using libraries between shell scripts. These can also be used to define common variables, as we shall see.
# common.lib
# Note no #!/bin/sh as this should not spawn
# an extra shell. It's not the end of the world
# to have one, but clearer not to.
#
STD_MSG="About to rename some files..."
rename()
{
# expects to be called as: rename .txt .bak
FROM=$1
TO=$2
for i in *$FROM
do
j=`basename $i $FROM`
mv $i ${j}$TO
done
}
#!/bin/sh
# function2.sh
. ./common.lib
echo $STD_MSG
rename .txt .bak
#!/bin/sh
# function3.sh
. ./common.lib
echo $STD_MSG
rename .html .html-bak
Hints and tips
You may have heard it said, that, with *nix, “everything is a file” - it’s true.
- 强大的文本分析工具
grep
wc
&& awk
Consider wc
, which counts the number of characters, lines, and words in a text file. Its output is:
$ wc hello.c
5 8 67 hello.c
If we want to get the number of lines into a variable, simply using:
$ NO_LINES=`wc -l hello.c`
$ echo $NO_LINES
5 hello.c
Because the output is space-padded, we can’t reliably get the number 102
into the string. Instead, we use the fact that awk
works similarly to scanf
in C - it strips unwanted whitespace. It puts these into variables $1 $2 $3
etc. So we use this construct:
$ NO_LINES=`wc -l hello.c | awk '{ print $1 }' `
$ echo $NO_LINES
5
sed
we can quickly use the s/from/to/g
construct by invoking sed
.For example:
sed s/eth0/eth1/g file1 > file2
changes every instance of eth0
in file1 to eth1
in file2.
If we were only changing a single character, tr
would be the tool to use, being smaller and therefore faster to load.
Another thing that tr
can’t do, is remove characters from a file:
echo ${SOMETHING} | sed s/"bad word"//g
This removes the phrase “bad word” from the variable ${SOMETHING}
. It may be tempting to say, “But grep
can do that!” - grep only deals with whole lines. Consider the file:
This line is okay.
This line contains a bad word. Treat with care.
This line is fine, too.
Grep would remove the whole second line, leaving only a two-line file; sed would change the file to read:
This line is okay.
This line contains a . Treat with care.
This line is fine, too.
quick referrence
Command | Description | Example |
---|---|---|
& | Run the previous command in the background | ls & |
&& | Logical AND | if [ "$foo" -ge "0" ] && [ "$foo" -le "9"] |
|| | Logical OR | `if [ “$foo” -lt “0” ] |
^ | Start of line | grep "^foo" |
$ | End of line | grep "foo$" |
= | String equality (cf. -eq) | if [ "$foo" = "bar" ] |
! | Logical NOT | if [ "$foo" != "bar" ] |
$$ | PID of current shell | echo "my PID = $$" |
$! | PID of last background command | ls & echo "PID of ls = $!" |
$? | exit status of last command | ls ; echo "ls returned code $?" |
$0 | Name of current command (as called) | echo "I am $0" |
$1 | Name of current command’s first parameter | echo "My first argument is $1" |
$9 | Name of current command’s ninth parameter | echo "My ninth argument is $9" |
$@ | All of current command’s parameters (preserving whitespace and quoting) | echo "My arguments are $@" |
$* | All of current command’s parameters (not preserving whitespace and quoting) | echo "My arguments are $*" |
-eq | Numeric Equality | if [ "$foo" -eq "9" ] |
-ne | Numeric Inquality | if [ "$foo" -ne "9" ] |
-lt | Less Than | if [ "$foo" -lt "9" ] |
-le | Less Than or Equal | if [ "$foo" -le "9" ] |
-gt | Greater Than | if [ "$foo" -gt "9" ] |
-ge | Greater Than or Equal | if [ "$foo" -ge "9" ] |
-z | String is zero length | if [ -z "$foo" ] |
-n | String is not zero length | if [ -n "$foo" ] |
-nt | Newer Than | if [ "$file1" -nt "$file2" ] |
-d | Is a Directory | if [ -d /bin ] |
-f | Is a File | if [ -f /bin/ls ] |
-r | Is a readable file | if [ -r /bin/ls ] |
-w | Is a writable file | if [ -w /bin/ls ] |
-x | Is an executable file | if [ -x /bin/ls ] |
( … ) | Function definition | function myfunc() { echo hello } |