A function may return a value in one of four different ways:
- Change the state of a variable or variables
- Use the
exit
command to end the shell script- Use the
return
command to end the function, and return the supplied value to the calling section of the shell script- echo output to stdout, which will be caught by the caller just as c=`expr $a + $b` is caught
[maxwell@oracle-db-19c shell_20230320]$ cat function.sh
#!/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..."
[maxwell@oracle-db-19c shell_20230320]$
Scope of Variables
Programmers used to other languages may be surprised at the scope rules for shell functions. Basically, there is no scoping, other than the parameters (
$1
,$2
,$@
, etc).
The$@
parameters are changed within the function to reflect how the function was called. The variablex
, however, is effectively a global variable -myfunc
changed it, and that change is still effective when control returns to the main script.
$ ./scope.sh a b c
Script was called with a b c
x is 1
I was called as : 1 2 3
x is 2
$
$ cat scope.sh
#!/bin/sh
myfunc()
{
echo "I was called as : $@"
x=2
}
### Main script starts here
echo "Script was called with $@"
x=1
echo "x is $x"
myfunc 1 2 3
echo "x is $x"
$
Recursion
Functions can be recursive - here's a simple example of a factorial function:
$ ./factorial.sh
Enter a number:
4
24
Enter a number:
2
2
Enter a number:
5
120
Enter a number:
6
720
Enter a number:
7
5040
Enter a number:
^C
$ cat 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
$
$
$ cat 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
}
$ cat function2.sh
#!/bin/sh
#function2.sh
. ./common.lib
echo $STD_MSG
rename .txt .bak
$
$ cat function3.sh
#!/bin/sh
# function2.sh
. ./common.lib
echo $STD_MSG
rename .html .html-bak
$
Return Codes
#!/bin/sh
adduser()
{
USER=$1
PASSWORD=$2
shift ; shift
COMMENTS=$@
useradd -c "${COMMENTS}" $USER
if [ "$?" -ne "0" ]; then
echo "Useradd failed"
return 1
fi
passwd $USER $PASSWORD
if [ "$?" -ne "0" ]; then
echo "Setting password failed"
return 2
fi
echo "Added user $USER ($COMMENTS) with pass $PASSWORD"
}
## Main script starts here
adduser bob letmein Bob Holness from Blockbusters
ADDUSER_RETURN_CODE=$?
if [ "$ADDUSER_RETURN_CODE" -eq "1" ]; then
echo "Something went wrong with useradd"
elif [ "$ADDUSER_RETURN_CODE" -eq "2" ]; then
echo "Something went wrong with passwd"
else
echo "Bob Holness added to the system."
fi
grep
is an extremely useful utility for the shell script programmer.
An example of grep would be:
$
$ grep -i maxwell /etc/passwd
maxwell:x:1000:1000:maxwell:/home/maxwell:/bin/bash
$ grep -i maxwell /etc/passwd | cut -d: -f1
maxwell
$ vim grep_maxwell.sh
$ chmod 775 grep_maxwell.sh
$
$ ./grep_maxwell.sh
All users with the word "maxwell" in their passwd
Entries are: maxwell
$
$ grep -i maxwell /etc/passwd
maxwell:x:1000:1000:maxwell:/home/maxwell:/bin/bash
$
$ grep -i maxwell /etc/passwd |cut -d: -f1
maxwell
$
$ cat grep_maxwell.sh
#!/bin/sh
maxwell=`grep -i maxwell /etc/passwd | cut -d: -f1`
echo "All users with the word \"maxwell\" in their passwd"
echo "Entries are: $maxwell"
$
$ vim grep_maxwell_tr.sh
$ chmod 775 grep_maxwell_tr.sh
$
$ ./grep_maxwell_tr.sh
All users with the word maxwell in their passwd
Entries are:
MAXWELL
$
$ cat grep_maxwell_tr.sh
maxwell=`grep -i maxwell /etc/passwd | cut -d: -f1`
echo "All users with the word "maxwell" in their passwd"
echo "Entries are: "
echo "$maxwell" | tr ' ' '\012' | tr '[a-z]' '[A-Z]'
$
Cheating
Cheating with awk
Consider
wc
, which counts the number of characters, lines, and words in a text file.
[maxwell@oracle-db-19c shell_20230320]$ PS1="$ " ; export PS1
$
$ wc grep_maxwell.sh
4 23 150 grep_maxwell.sh
$
AWK是一种用于文本处理的编程语言和工具,可在shell中使用。AWK以行为单位对输入数据进行处理,并按照用户指定的规则进行分割、过滤和格式化输出。
AWK命令由三部分组成:模式(pattern)、动作(action)和输入文件(input file)。模式用来匹配输入数据中的某些内容,动作定义了当模式匹配到输入数据时要执行的操作,输入文件则指定要处理的数据源。
常见的AWK用途包括:
- 格式化文本数据输出
- 进行文本搜索和替换
- 统计和分析文本数据
If we want to get the number of lines into a variable, simply using:
$ NO_LINES=`wc -l grep_maxwell.sh | awk '{print $1}'`
$ echo ${NO_LINES}
4
$
Cheating with sed
Another handy utility is sed - the stream editor. we can quickly use the
s/from/to/g
construct by invokingsed
.
$ touch test.txt
$ vim test.txt
$
$ cat test.txt
This line is okay.
This line contains a bad word. Treat with care.
This line is fine, too.
There is nothing wrong with cheating! Some things the shell just isn't very good at. Two useful tools are sed and awk. Whilst these are two hugely powerful utilities, which can be used as mini- programming languages in their own right, they are often used in shell scripts for very simple, specific reasons.
$
$ sed s/line/lines/g test.txt > test_result.txt
$ cat test_result.txt
This lines is okay.
This lines contains a bad word. Treat with care.
This lines is fine, too.
There is nothing wrong with cheating! Some things the shell just isn't very good at. Two useful tools are sed and awk. Whilst these are two hugely powerful utilities, which can be used as mini- programming languages in their own right, they are often used in shell scripts for very simple, specific reasons.
$
sed是一种流编辑器,也是在shell中用于文本处理的工具。它可以按照指定的规则对输入数据进行编辑、替换、删除和添加等操作,并将结果输出到标准输出或者文件中。
sed命令由一个或多个编辑命令组成,每个命令都包含一个地址范围和一个操作。地址范围指定了进行操作的行或者行的范围,操作则定义了要执行的编辑动作。
常见的sed用途包括:
- 查找和替换文本
- 对文本进行格式化输出
- 选择性地显示或删除文本行
15. Quick Reference
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" ] || [ "$foo" -gt "9" ]
^ 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 }
IFS
在Shell中,IFS是"Internal Field Separator"的缩写。它是一个环境变量,用于指定用于分隔字段的字符或字符串。当解析命令时,Shell使用这个变量来确定如何拆分输入行的单词和空格。
默认情况下,IFS设置为包含空格、制表符和换行符的一组字符。这意味着Shell将使用这些字符来拆分命令行参数和标准输入数据流。
可以通过在脚本或命令中更改IFS值来自定义分隔符。例如,可以将IFS设置为逗号 "," 以使Shell使用逗号来拆分输入行。注意,在更改IFS之前,请保存原始值,并在完成后还原它,以避免对程序的其他部分造成影响。
IFS是一个环境变量,通常用于Shell脚本中,以指定分隔符来处理文本数据。以下是IFS的一些用法:
- 读取文件行并拆分字段:可以使用while循环读取文件的每一行,并在每次迭代时设置IFS为适当的分隔符(例如逗号或制表符),然后使用read命令将行拆分为字段。
#!/bin/bash
while IFS=',' read -r col1 col2 col3
do
echo "Column 1: $col1"
echo "Column 2: $col2"
echo "Column 3: $col3"
done < data.csv
2.拆分字符串:可以使用IFS来拆分字符串为数组。例如,以下代码将使用空格作为分隔符拆分字符串为数组:
#!/bin/bash
string="Hello World"
IFS=' ' read -ra arr <<< "$string"
for i in "${arr[@]}"
do
echo "$i"
done
3.实现命令替换:可以使用$(command)或反引号字符来执行命令,并使用IFS来拆分输出为数组。例如,以下代码使用IFS将df命令的输出拆分为一个数组:
#!/bin/bash
IFS=$'\n' df_output=($(df -h))
for line in "${df_output[@]}"
do
echo "$line"
done
IFS是一个非常有用的变量,可用于处理文本数据和实现各种Shell编程任务。