bash


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  28  2022 '/usr/bin/['
$ ls -l /usr/bin/test
-rwxr-xr-x 1 root root 43440  28  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:


var5.sh

#!/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:


factorial.sh

#!/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

# 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
}

function2.sh

#!/bin/sh
# function2.sh
. ./common.lib
echo $STD_MSG
rename .txt .bak

function3.sh

#!/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 }

文章作者: hugo
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hugo !
  目录