Bash-скрипты, часть 9: регулярные выражения
Содержание:
- Using variables
- 9.2.2. Examples
- Double Quotes
- Control bash loop with
- Bash Functions
- Bash Select
- Case statement conditional
- Переменные в скриптах
- How do they work?
- File comparisons
- Global vs. Local variables
- Passing arguments to the bash script
- Executing shell commands with bash
- Reading User Input
- Bash Trap Command
- Bash Arithmetics
- Nested If statements
- Introduction
- Bash nested if Statements
- Test
- Summary
- String Comparisons
- Bash File Testing
Using variables
Variables allow you to store information to use it in your script.
You can define two types of variables in your shell script:
- Environment variables
- User variables
Environment variables
Sometimes you need to interact with system variables; you can do this by using environment variables.
#!/bin/bash # display user home echo "Home for the current user is: $HOME"
Notice that we put the $HOME system variable between double quotations, and it prints the home variable correctly.
What if we want to print the dollar sign itself?
echo "I have $1 in my pocket"
Because variable $1 doesn’t exist, it won’t work. So how to overcome that?
You can use the escape character, which is the backslash \ before the dollar sign like this:
echo "I have \$1 in my pocket"
Now it works!!
User variables
Also, you can set and use your custom variables in the script.
You can call user variables in the same way as this:
#!/bin/bash # User variables grade=5 person="Adam" echo "$person is a good boy, he is in grade $grade"
chmod +x myscript ./myscript
9.2.2. Examples
9.2.2.1. Simple example using while
Here is an example for the impatient:
#!/bin/bash # This script opens 4 terminal windows. i="0" while do xterm & i=$ done |
9.2.2.2. Nested while loops
The example below was written to copy pictures that are made with a webcam to a web directory. Every five minutes a picture is taken. Every hour, a new directory is created, holding the images for that hour. Every day, a new directory is created containing 24 subdirectories. The script runs in the background.
#!/bin/bash # This script copies files from my homedirectory into the webserver directory. # (use scp and SSH keys for a remote directory) # A new directory is created every hour. PICSDIR=/home/carol/pics WEBDIR=/var/www/carol/webcam while true; do DATE=`date +%Y%m%d` HOUR=`date +%H` mkdir $WEBDIR/"$DATE" while ; do DESTDIR=$WEBDIR/"$DATE"/"$HOUR" mkdir "$DESTDIR" mv $PICDIR/*.jpg "$DESTDIR"/ sleep 3600 HOUR=`date +%H` done done |
Note the use of the true statement. This means: continue execution until we are forcibly interrupted (with kill or Ctrl+C).
This small script can be used for simulation testing; it generates files:
#!/bin/bash # This generates a file every 5 minutes while true; do touch pic-`date +%s`.jpg sleep 300 done |
Note the use of the date command to generate all kinds of file and directory names. See the man page for more.
Use the system | |
---|---|
The previous example is for the sake of demonstration. Regular checks can easily be achieved using the system’s cron facility. Do not forget to redirect output and errors when using scripts that are executed from your crontab! |
9.2.2.3. Using keyboard input to control the while loop
This script can be interrupted by the user when a Ctrl+C sequence is entered:
#!/bin/bash # This script provides wisdom FORTUNE=/usr/games/fortune while true; do echo "On which topic do you want advice?" cat << topics politics startrek kernelnewbies sports bofh-excuses magic love literature drugs education topics echo echo -n "Make your choice: " read topic echo echo "Free advice on the topic of $topic: " echo $FORTUNE $topic echo done |
A here document is used to present the user with possible choices. And again, the true test repeats the commands from the CONSEQUENT-COMMANDS list over and over again.
9.2.2.4. Calculating an average
This script calculates the average of user input, which is tested before it is processed: if input is not within range, a message is printed. If q is pressed, the loop exits:
#!/bin/bash # Calculate the average of a series of numbers. SCORE="0" AVERAGE="0" SUM="0" NUM="0" while true; do echo -n "Enter your score ('q' for quit): "; read SCORE; if (("$SCORE" < "0")) || (("$SCORE" > "100")); then echo "Be serious. Common, try again: " elif ; then echo "Average rating: $AVERAGE%." break else SUM=$ NUM=$ AVERAGE=$[$SUM / $NUM] fi done echo "Exiting." |
Note how the variables in the last lines are left unquoted in order to do arithmetic.
Double Quotes
Double quotes in bash will suppress special meaning of every meta characters except «$», «\» and «`». Any other meta characters will be read literally. It is also possible to use single quote within double quotes. If we need to use double quotes within double quotes bash can read them literally when escaping them with «\». Example:
#!/bin/bash#Declare bash string variableBASH_VAR="Bash Script"# echo variable BASH_VARecho $BASH_VAR# meta characters and its special meaning in bash is # suppressed when using double quotes except "$", "\" and "`"echo "It's $BASH_VAR and \"$BASH_VAR\" using backticks: `date`"
Control bash loop with
Here is a example of while loop controlled by standard input. Until the redirection chain from STDOUT to STDIN to the read command exists the while loop continues.
#!/bin/bash# This bash script will locate and replace spaces# in the filenamesDIR="."# Controlling a loop with bash read command by redirecting STDOUT as# a STDIN to while loop# find will not truncate filenames containing spacesfind $DIR -type f | while read file; do# using POSIX class to find space in the filenameif ]* ]]; then# substitute space with "_" character and consequently rename the filemv "$file" `echo $file | tr ' ' '_'`fi;# end of while loopdone
Bash Functions
!/bin/bash# BASH FUNCTIONS CAN BE DECLARED IN ANY ORDERfunction function_B { echo Function B.}function function_A { echo $1}function function_D { echo Function D.}function function_C { echo $1}# FUNCTION CALLS# Pass parameter to function Afunction_A "Function A."function_B# Pass parameter to function Cfunction_C "Function C."function_D
Bash Select
#!/bin/bashPS3='Choose one word: ' # bash selectselect word in "linux" "bash" "scripting" "tutorial" do echo "The word you have selected is: $word"# Break, otherwise endless loop break doneexit 0
Case statement conditional
#!/bin/bashecho "What is your preferred programming / scripting language"echo "1) bash"echo "2) perl"echo "3) phyton"echo "4) c++"echo "5) I do not know !"read case;#simple case bash structure# note in this case $case is variable and does not have to# be named case this is just an examplecase $case in 1) echo "You selected bash";; 2) echo "You selected perl";; 3) echo "You selected phyton";; 4) echo "You selected c++";; 5) exitesac
Переменные в скриптах
Написание скриптов на Bash редко обходится без сохранения временных данных, а значит создания переменных. Без переменных не обходится ни один язык программирования и наш примитивный язык командной оболочки тоже.
Возможно, вы уже раньше встречались с переменными окружения. Так вот, это те же самые переменные и работают они аналогично.
Например, объявим переменную string:
Значение нашей строки в кавычках. Но на самом деле кавычки не всегда нужны. Здесь сохраняется главный принцип bash — пробел — это специальный символ, разделитель, поэтому если не использовать кавычки world уже будет считаться отдельной командой, по той же причине мы не ставим пробелов перед и после знака равно.
Чтобы вывести значение переменной используется символ $. Например:
Модифицируем наш скрипт:
И проверяем:
$ ./script
Hello world
Bash не различает типов переменных так, как языки высокого уровня, например, С++, вы можете присвоить переменной как число, так и строку. Одинаково все это будет считаться строкой. Оболочка поддерживает только слияние строк, для этого просто запишите имена переменных подряд:
Проверяем:
Обратите внимание, что как я и говорил, кавычки необязательны если в строке нет спецсимволов. Присмотритесь к обоим способам слияния строк, здесь тоже демонстрируется роль кавычек
Если же вам нужны более сложные способы обработки строк или арифметические операции, это не входит в возможности оболочки, для этого используются обычные утилиты.
How do they work?
This is just a little bit of background knowledge. It’s not necessary to understand this in order to write scripts but it can be useful to know once you start getting into more complex scripts (and scripts that call and rely on other scripts once you start getting really fancy).
In the realm of Linux (and computers in general) we have the concept of programs and processes. A program is a blob of binary data consisting of a series of instructions for the CPU and possibly other resources (images, sound files and such) organised into a package and typically stored on your hard disk. When we say we are running a program we are not really running the program but a copy of it which is called a process. What we do is copy those instructions and resources from the hard disk into working memory (or RAM). We also allocate a bit of space in RAM for the process to store variables (to hold temporary working data) and a few flags to allow the operating system (OS) to manage and track the process during it’s execution.
Essentially a process is a running instance of a program.
There could be several processes representing the same program running in memory at the same time. For example I could have two terminals open and be running the command cp in both of them. In this case there would be two cp processes currently existing on the system. Once they are finished running the system then destroys them and there are no longer any processes representing the program cp.
When we are at the terminal we have a Bash process running in order to give us the Bash shell. If we start a script running it doesn’t actually run in that process but instead starts a new process to run inside. We’ll demonstrate this in the next section on variables and it’s implications should become clearer. For the most part you don’t need to worry too much about this phenomenon however.
File comparisons
You can compare and check for files using the following operators:
-d my_file Checks if its a folder.
-e my_file Checks if the file is available.
-f my_file Checks if its a file.
-r my_file Checks if it’s readable.
my_file –nt my_file2 Checks if my_file is newer than my_file2.
my_file –ot my_file2 Checks if my_file is older than my_file2.
-O my_file Checks if the owner of the file and the logged user match.
-G my_file Checks if the file and the logged user have an identical group.
As they imply, you will never forget them.
Let’s pick one of them and take it as an example:
#!/bin/bash mydir=/home/likegeeks if ; then echo "Directory $mydir exists" cd $mydir ls else echo "NO such file or directory $mydir" fi
We are not going to type every one of them as an example. You just type the comparison between the square brackets as it is and complete your script normally.
There are some other advanced if-then features, but let’s make it in another post.
That’s for now. I hope you enjoy it and keep practicing more and more.
Thank you.
Global vs. Local variables
#!/bin/bash#Define bash global variable#This variable is global and can be used anywhere in this bash scriptVAR="global variable"function bash {#Define bash local variable#This variable is local to bash function onlylocal VAR="local variable"echo $VAR}echo $VARbash# Note the bash global variable did not change# "local" is bash reserved wordecho $VAR
Passing arguments to the bash script
#!/bin/bash# use predefined variables to access passed arguments#echo arguments to the shellecho $1 $2 $3 ' -> echo $1 $2 $3'# We can also store arguments from bash command line in special arrayargs=("$@")#echo arguments to the shellecho ${args} ${args} ${args} ' -> args=("$@"); echo ${args} ${args} ${args}'#use $@ to print out all arguments at onceecho $@ ' -> echo $@'# use $# variable to print out# number of arguments passed to the bash scriptecho Number of arguments passed: $# ' -> echo Number of arguments passed: $#'
/arguments.sh Bash Scripting Tutorial
Executing shell commands with bash
#!/bin/bash# use backticks " ` ` " to execute shell commandecho `uname -o`# executing bash command without backticksecho uname -o
Reading User Input
#!/bin/bashecho -e "Hi, please type the word: \c "read wordecho "The word you entered is: $word"echo -e "Can you please enter two words? "read word1 word2echo "Here is your input: \"$word1\" \"$word2\""echo -e "How do you feel about bash scripting? "# read command now stores a reply into the default build-in variable $REPLYreadecho "You said $REPLY, I'm glad to hear that! "echo -e "What are your favorite colours ? "# -a makes read command to read into an arrayread -a coloursecho "My favorite colours are also ${colours}, ${colours} and ${colours}:-)"
Bash Trap Command
#!/bin/bash# bash trap commandtrap bashtrap INT# bash clear screen commandclear;# bash trap function is executed when CTRL-C is pressed:# bash prints message => Executing bash trap subrutine !bashtrap(){ echo "CTRL+C Detected !...executing bash trap !"}# for loop from 1/10 to 10/10for a in `seq 1 10`; do echo "$a/10 to Exit." sleep 1;doneecho "Exit Bash Trap Example!!!" |
Bash Arithmetics
#!/bin/bashecho '### let ###'# bash additionlet ADDITION=3+5echo "3 + 5 =" $ADDITION# bash subtractionlet SUBTRACTION=7-8echo "7 - 8 =" $SUBTRACTION # bash multiplicationlet MULTIPLICATION=5*8echo "5 * 8 =" $MULTIPLICATION# bash divisionlet DIVISION=4/2echo "4 / 2 =" $DIVISION# bash moduluslet MODULUS=9%4echo "9 % 4 =" $MODULUS# bash power of twolet POWEROFTWO=2**2echo "2 ^ 2 =" $POWEROFTWOecho '### Bash Arithmetic Expansion ###'# There are two formats for arithmetic expansion: $ # and $(( expression #)) its your choice which you useecho 4 + 5 = $((4 + 5))echo 7 - 7 = $echo 4 x 6 = $((3 * 2))echo 6 / 3 = $((6 / 3))echo 8 % 7 = $((8 % 7))echo 2 ^ 8 = $echo '### Declare ###'echo -e "Please enter two numbers \c"# read user inputread num1 num2declare -i resultresult=$num1+$num2echo "Result is:$result "# bash convert binary number 10001result=2#10001echo $result# bash convert octal number 16result=8#16echo $result# bash convert hex number 0xE6Aresult=16#E6Aecho $result
Nested If statements
Talking of indenting. Here’s a perfect example of when it makes life easier for you. You may have as many if statements as necessary inside your script. It is also possible to have an if statement inside of another if statement. For example, we may want to analyse a number given on the command line like so:
nested_if.sh
- #!/bin/bash
- if
- then
- echo Hey that\’s a large number.
- if (( $1 % 2 == 0 ))
- then
- echo And is also an even number.
- fi
- fi
Let’s break it down:
- Line 4 — Perform the following, only if the first command line argument is greater than 100.
- Line 8 — This is a light variation on the if statement. If we would like to check an expression then we may use the double brackets just like we did for variables.
- Line 10 — Only gets run if both if statements are true.
Yo dawg, I herd you like if statements so I put an if statement inside your if statement.
Xzibit
(Xzibit didn’t actually say that but I’m sure he would have, had he hosted Pimp My Bash Script.)
You can nest as many if statements as you like but as a general rule of thumb if you need to nest more than 3 levels deep you should probably have a think about reorganising your logic.
Introduction
Bash if statements are very useful. In this section of our Bash Scripting Tutorial you will learn the ways you may use if statements in your Bash scripts to help automate tasks.
If statements (and, closely related, case statements) allow us to make decisions in our Bash scripts. They allow us to decide whether or not to run a piece of code based upon conditions that we may set. If statements, combined with loops (which we’ll look at in the next section) allow us to make much more complex scripts which may solve larger tasks.
Like what we have looked at in previous sections, their syntax is very specific so stay on top of all the little details.
Bash nested if Statements
Nested if are useful in the situation where one condition will be checked based on results of outer condition.
if then if then <code block> else <code block> fi else if then <code block> fi fi
For example below small shell program is for finding the greatest value between 3 values taken input by user. This program will work with numeric values only. If two values are similar it will print only one value.
#!/bin/bash read -p "Enter value of i :" i read -p "Enter value of j :" j read -p "Enter value of k :" k if then if then echo "i is greatest" else echo "k is greatest" fi else if then echo "j is greatest" else echo "k is greatest" fi fi
Test
The square brackets ( ) in the if statement above are actually a reference to the command test. This means that all of the operators that test allows may be used here as well. Look up the man page for test to see all of the possible operators (there are quite a few) but some of the more common ones are listed below.
Operator | Description |
---|---|
! EXPRESSION | The EXPRESSION is false. |
-n STRING | The length of STRING is greater than zero. |
-z STRING | The lengh of STRING is zero (ie it is empty). |
STRING1 = STRING2 | STRING1 is equal to STRING2 |
STRING1 != STRING2 | STRING1 is not equal to STRING2 |
INTEGER1 -eq INTEGER2 | INTEGER1 is numerically equal to INTEGER2 |
INTEGER1 -gt INTEGER2 | INTEGER1 is numerically greater than INTEGER2 |
INTEGER1 -lt INTEGER2 | INTEGER1 is numerically less than INTEGER2 |
-d FILE | FILE exists and is a directory. |
-e FILE | FILE exists. |
-r FILE | FILE exists and the read permission is granted. |
-s FILE | FILE exists and it’s size is greater than zero (ie. it is not empty). |
-w FILE | FILE exists and the write permission is granted. |
-x FILE | FILE exists and the execute permission is granted. |
A few points to note:
- = is slightly different to -eq. will return false as = does a string comparison (ie. character for character the same) whereas -eq does a numerical comparison meaning will return true.
- When we refer to FILE above we are actually meaning a path. Remember that a path may be absolute or relative and may refer to a file or a directory.
- Because is just a reference to the command test we may experiment and trouble shoot with test on the command line to make sure our understanding of its behaviour is correct.
- test 001 = 1
- echo $?
- 1
- test 001 -eq 1
- echo $?
- touch myfile
- test -s myfile
- echo $?
- 1
- ls /etc > myfile
- test -s myfile
- echo $?
Let’s break it down:
- Line 1 — Perform a string based comparison. Test doesn’t print the result so instead we check it’s exit status which is what we will do on the next line.
- Line 2 — The variable $? holds the exit status of the previously run command (in this case test). 0 means TRUE (or success). 1 = FALSE (or failure).
- Line 4 — This time we are performing a numerical comparison.
- Line 7 — Create a new blank file myfile (assuming that myfile doesn’t already exist).
- Line 8 — Is the size of myfile greater than zero?
- Line 11 — Redirect some content into myfile so it’s size is greater than zero.
- Line 12 — Test the size of myfile again. This time it is TRUE.
Summary
- if
- Perform a set of commands if a test is true.
- else
- If the test is not true then perform a different set of commands.
- elif
- If the previous test returned false then try this one.
- &&
- Perform the and operation.
- ||
- Perform the or operation.
- case
- Choose a set of commands to execute depending on a string matching a particular pattern.
- Indenting
- Indenting makes your code much easier to read. It get’s increasingly important as your Bash scripts get longer.
- Planning
- Now that your scripts are getting a little more complex you will probably want to spend a little bit of time thinking about how you structure them before diving in.
String Comparisons
= | equal |
!= | not equal |
< | less then |
> | greater then |
-n s1 | string s1 is not empty |
-z s1 | string s1 is empty |
#!/bin/bash#Declare string S1S1="Bash"#Declare string S2S2="Scripting"if ; then echo "Both Strings are equal"else echo "Strings are NOT equal"fi
#!/bin/bash#Declare string S1S1="Bash"#Declare string S2S2="Bash"if ; then echo "Both Strings are equal"else echo "Strings are NOT equal"fi
Bash File Testing
-b filename | Block special file |
-c filename | Special character file |
-d directoryname | Check for directory existence |
-e filename | Check for file existence |
-f filename | Check for regular file existence not a directory |
-G filename | Check if file exists and is owned by effective group ID. |
-g filename | true if file exists and is set-group-id. |
-k filename | Sticky bit |
-L filename | Symbolic link |
-O filename | True if file exists and is owned by the effective user id. |
-r filename | Check if file is a readable |
-S filename | Check if file is socket |
-s filename | Check if file is nonzero size |
-u filename | Check if file set-ser-id bit is set |
-w filename | Check if file is writable |
-x filename | Check if file is executable |
#!/bin/bashfile="./file"if ; then echo "File exists"else echo "File does not exists"fi
Similarly for example we can use while loop to check if file does not exists. This script will sleep until file does exists. Note bash negator «!» which negates the -e option.
#!/bin/bashwhile ; do# Sleep until file does exists/is createdsleep 1done