Degtyarev E.K. Shell is an interpreter of commands issued from the terminal or from a batch file. This is a regular program (i.e. not included in the kernel operating system UNIX). You can replace it with another one or have several. The two most famous versions: - Shell (version 7 UNIX) or Bourne Shell (from the name of the author S.R.Bourne from Bell Labs); - C-Shell (Berkley UNIX versions). They are similar, but there are differences: C-Shell is more powerful in dialog mode, and regular Shell has more elegant control structures. Shell is a programming language because it has: - variables; - control structures (type if); - subroutines (including command files); - transfer of parameters; - interrupt handling.

7.2. Session start file (login file)

Regardless of the version of Shell, when you log in to a UNIX system, it looks for a session logon file with a predefined name in order to execute it as batch file; - for UNIX version 7 this is: .profile; - for C-Shell this is: .login and/or .cshrc. The following commands are usually placed in this file: - setting terminal characteristics; - notifications like who, date; - setting command search directories (usually: /bin, /usr/bin); - changing the hint from $ to another symbol, etc.

7.3. Shell procedure

This is a batch file. There are two ways to call it for execution: 1. $ sh dothat (where dothat is some batch file); 2. $ chmod 755 dothat (make it executable, i.e. -rwxr-xr-x) $ dothat. You should know the search order for command directories (by default): - current; - system /bin; - system /usr/bin. Therefore, if the name of your batch file duplicates the name of a command in the system directories, the latter will become unavailable (unless you type its full name).

7.4. Shell Variables

In Shell version 7, a variable definition contains a name and a value: var = value. Variable access is by name with a $ sign in front: fruit = apple (definition); echo $fruit(access); apple (echo result). So a variable is a string. Concatenation of strings is possible: $ fruit = apple $ fruit = pine$fruit $ echo $fruit pineapple $ fruite = apple $ wine = $(fruit)jack $ echo $wine applejack $ Other ways to set the value of a variable - input from a file or output from a command (see Section 7.6), as well as assigning values ​​to a variable - a parameter of the for loop from a list of values ​​specified explicitly or by default (see Section 7.9). The variable can be: 1) Part of the full file name: $d/filename, where $d is a variable (for example, d = /usr/bin). 2) Part of the command: $ S = "sort + 2n + 1 - 2" (spaces require quotes "") $ $S tennis/lpr $ $S basketball/lpr $ $S pingpong/lpr $ However, inside the values ​​for commands cannot contain the symbols |, >,

7.5. Predefined Shell Variables

Some of them can only be read. The most common: HOME - the user’s “home” directory; serves as the default argument to cd; PATH is the set of directories in which UNIX looks for commands; PS1 is the primary prompt (string) of the system (for v.7 - $). Changing PS1 (hint) is usually done in the login file, for example: PS1 = ? or PS1 = "? " (with a space, which is more convenient). Changing PATH: $ echo $PATH - look; :/bin:/usr/bin - PATH value; $cd - "home"; $ mkdir bin - new directory; $ echo $HOME - look; /users/maryann - current directory; $ PATH = :$HOME/bin:$PATH - change PATH; $ echo $PATH - look; :/users/maryann/bin:/bin:/usr/bin - new PATH value.

7.6. Setting a Shell variable with command output

Example 1: $ now = `date` (where `` are backticks) $ echo $now Sun Feb 14 12:00:01 PST 1985 $ Example 2: (getting variable value from file): $ menu = `cat food` $ echo $menu apples cheddar chardonnay (carriage returns are replaced with spaces).

7.7. Shell Variables - Procedure Arguments

These are a special type of variable called numbers. Example: $ dothis grapes apples pears (procedure). Then the positional parameters (arguments) of this command are available by name: $1 = `grapes` $2 = `apples` $3 = `pears` etc. up to $9. However, there is a shift command that shifts the names to the remaining arguments if there are more than 9 of them (window width 9). Another way to get all the arguments (even if there are more than 9): $*, which is equivalent to $1$2... The number of arguments is assigned to another variable: $#(sharp). Finally, the procedure name is $0; the variable $0 is not taken into account when calculating $#.

7.8. Shell Structural Operators

In addition to procedures, the Shell language has structural operators such as “if-else” and “while-do”. Shell programming, in addition to writing procedures, is used for: - testing the algorithm before coding it in C or FORTRAN-77 (no compilation, linking, loading, easy debugging); - teaching programming principles to non-programmers.

7.9. The for loop operator

Let there be a batch file makelist (procedure) $ cat makelist sort +1 -2 people | tr -d -9 | pr -h Distribution | lpr. If instead of one people file there are several, for example: adminpeople, hardpeople, softpeople,..., then it is necessary to repeat the procedure with different files. This is possible using the for operator. Syntax: for in do done The keywords for, do, done are written from the beginning of the line. Example (we will change the makelist procedure) for file in adminpeople, hardpeople, softpeople do Sort +1 -2 $file | tr... | lpr done. You can use Shell metacharacters in a list of values. Example: for file in *people (for all names ending in people) do ... done. If in is omitted, then by default the list of values ​​is taken as the list of arguments of the procedure that contains the loop, and if the loop is not in the procedure, then the list of command line parameters (that is, the command acts as the procedure). Example: for file do ... done Calling makelist adminpeople hardpeople softpeople will do the same.

7.10. Conditional if statement

We use variable names that represent the values ​​of the procedure parameters: sort +1 -2 $1 | tr... | lpr Example of an invalid call: makelist (no parameters), where $1 is undefined. You can correct the error by checking the number of arguments - the value of the $# variable using the if - operator. Example: (modified makelist procedure): if test $# -eq 0 then echo "You must give a filename" exit 1 else sort +1 -2 $1 | tr... | lpr fi Here test and exit are test commands (see section 7.11) and exit. Thus, the syntax of the if statement is: if ; then ; The keywords if, then, else and fi are written from the beginning of the line. Successful execution procedure means that it returns the value true = 0 (zero) (failure - the return value is not equal to 0). The exit 1 statement sets the return value 1 for the makelist to fail and ends the procedure. Nested ifs are possible. For else if there is an abbreviation elif, which simultaneously abbreviates fi.

7.11. "test" command

Not part of Shell, but used inside Shell procedures. There are three types of checks: - evaluation of numerical values; - file type assessment; - string evaluation. Each type has its own primitives (operations op). For numbers, the syntax is as follows: N op M, where N, M are numbers or numeric variables; op takes the values: -eq, -ne, gt, -lt, -ge, -le (with the usual meaning, as in FORTRAN, for example). For a file, the syntax is: op filename, where op takes the values: -s (the file exists and is not empty); -f (file, not directory); -d (file-directory (directory); -w (file to write); -r (file to read). For strings, the syntax is: S op R, where S, R are strings or string variables or op1 S op takes values := (equivalence); != (not equivalence); op1 takes the values: -z (zero-length string); -n (non-zero-length string) Finally, a few checks different types can be merged logical operations-a (AND) and -o (OR). Examples: $ if test -w $2 -a -r S1 > then cat $1 >> $2 > else echo "cannot append" > fi $ On some UNIX operating systems, square brackets are used instead of the test command, i.e. if [...] instead of if test ... .

7.12. While loop operator

Syntax: while do done If the "command" is executed successfully, then execute the "commands" terminated by the done keyword. Example: if test $# -eq 0 then echo "Usage: $0 file ..." > &2 exit fi while test $# -gt 0 do if test -s $1 then echo "no file $1" > &2 else sort + 1 - 2 $1 | tr -d ... (procedures) fi shift (* renumber arguments *) done Procedures are executed on all arguments.

7.13. Until loop statement

Inverts the repeat condition from while Syntax: until do done Until the "command" completes successfully, execute commands ending with done. Example: if test S# -eq 0 then echo "Usage $0 file..." > &2 exit fi until test S# -eq 0 do if test -s $1 then echo "no file $1" > &2 else sort +1 - 2 $1 | tr -d ... (procedure) fi shift (argument shift) done Executed similarly to the previous one.

7.14. case selection operator

Syntax: case in string1) ;; string2) ;; string3) ... etc. ... esac Example: Let the procedure have the -t option, which can be supplied as the first parameter: ................. together = no case $1 in -t) together = yes shift ;; -?) echo "$0: no option $1" exit ;; esac if test $together = yes then sort ... fi where? - metacharacter (if -?, i.e. “other” option other than -t, then an error). All Shell metacharacters can be used, including ?, *, [-]. It's easy to add (in the example) other options simply by extending case.

7.15. Using temporary files in the /tmp directory

This is a special directory in which all files are writable by all users. If a procedure that creates a temporary file is used by several users, then it is necessary to ensure that the names are unique. created files. The standard trick is to name the temporary file $0$$, where $0 is the name of the procedure and $$ is a standard variable equal to the unique identification number of the process executing the current command. Although the administrator periodically deletes temporary files in /tmp, it is good practice to explicitly delete them after use.

7.16. Comments in procedures

They begin with a colon:, which is considered a null command, and the comment text is its argument. To prevent Shell from interpreting metacharacters ($, *, etc.), it is recommended to enclose the comment text in single quotes. On some UNIX operating systems, the note begins with a # sign.

7.17. Example procedure

:"This procedure works with files containing names" : "and phone numbers," :"sorts them together or separately and prints the result on" :"screen or printer" :"Procedure keys:" :" -t (together) - merge and sort all files together" :" -p (printer) - print files to the printer" if test $# - eq 0 then echo "Usage: $ 0 file ... " > & 2 exit fi together = no print = no while test $# -gt 0 do case $1 in -t) together = yes shift ;; -p) print = yes shift ;; -?) echo "$0: no option $1" exit ;; *) if test $together = yes then sort -u +1 -2 $1 | tr ... > /tmp/$0$$ if $print = no then cat /tmp/$0$$ else lpr -c /tmp/$0$$ fi rm /tmp/$0$$ exit else if test -s $1 then echo "no file $1" > &2 else sort +1 -2 $1 | tr...> /tmp/$0$$ if $print = no then cat /tmp/$0$$ else lpr -c /tmp/$0$$ fi rm /tmp/$0$$ fi shift fi;; esac done. The procedure checks the number of parameters $#, and if it is zero, it exits. Otherwise, it processes the parameters (case statement). The parameter can be either a key (a character preceded by a minus sign) or a file name (a string represented by the * metacharacter). If the key is not valid (the metacharacter ? is different from t and p), the procedure ends. Otherwise, depending on the presence of the t and p keys, the actions stated in the comment at the beginning of the procedure are performed.

7.18. Handling interrupts in procedures

If, while executing a procedure, an interrupt signal is received (from the BREAK or DEL key, for example), then all created temporary files will remain undeleted (until the administrator does this) due to the immediate termination of the process. The best solution is the processing of interrupts within a procedure using the trap operator: Syntax: trap "command arguments" signals... Quotes form the first argument of several commands, separated by a semicolon. They will be executed if an interrupt occurs, specified by the arguments signals (integers): 2 - when you interrupt the process; 1 - if you are stuck (disconnected from the system), etc. Example (development of the previous one): case $1 in ..... *) trap "rm /tmp/*; exit" 2 1 (deleting temporary files) if test -s $1 .............. rm /tmp/* It would be better: trap "rm /tmp/* > /dev/null; exit" 2 1 since the interrupt may happen before , as the file /tmp/$0$$ is created and an alarm message about this case is forwarded to the null device.

7.19. Performing arithmetic operations: expr

The expr command evaluates the value of the expression given as an argument and sends the result to standard output. The most interesting application is performing operations on language variables Shell. Example of summing 3 numbers: $ cat sum3 expr $1 + $2 + $3 $ chmod 755 sum3 $ sum3 13 49 2 64 $ Example of direct use of the command: $ expr 13 + 49 + 2 + 64 + 1 129 $ The following arithmetic operations can be used in expr - ry: +, -, *, /, % (remainder). All operands and operations must be separated by spaces. Note that the multiplication sign should be enclosed in quotation marks (single or double), for example: “*”, since the * symbol has a special meaning in Shell. A more complex example of expr in a procedure (snippet): num = "wc -l

7.20. Debugging Shell Procedures

There are three tools that allow you to debug procedures. 1) Placing echo commands in the body of the procedure to issue messages that are a trace of the procedure execution. 2) The -v (verbose = verbose) option in the Shell command causes the command to be printed on the screen before it is executed. 3) The -x (execute) option in the Shell command causes the command to be printed on the screen as it is executed, replacing all variables with their values; this is the most powerful tool.

for VAR in 1 2 3...N do done or in one line: for VAR in 1 2 3...N; do ; done
It is possible to substitute both numeric values ​​and ASCII characters into the variable.
Example: $ for i in 1 2 A B Abc ; do echo $i; done 1 2 A B Abc Example of listing files in a variable by “mask” for video transcoding: for i in*.avi; do ; done

2. Substituting the results of another command

for VAR in $(); do ; done
An example of using the results of the seq command: for i in$(seq [KEY]); do echo $i; done$ for i in $(seq 3); do echo $i; done 1 2 3 $ for i in $(seq 3 5); do echo $i; done 3 4 5 $ for i in $(seq 2 2 6); do echo $i; done 2 4 6 An example of using the results of the ls command: $ for i in $(ls /$HOME/Video); do echo $i; done 001.avi 002.avi 003.avi

3. Substitution using C-style (C-style)

for((EXPR1; EXPR2; EXPR3)) do <список команд> done for((i=1; i<=3 ; i++)); do echo $i; done$ for ((i=1; i<=3 ; i++)); do echo $i; done 1 2 3 Подробнее о применении C-style в Bash

4. Enumeration using curly braces (..)

The (START..END) syntax is supported starting with bash version 3.0+, and the (START..END..INCREMENT) syntax is supported starting with bash version 4.0+:

for VAR in {..} do done or for VAR in {....} do done Examples: $ for i in (1..3); do echo $i; done 1 2 3 or $ for i in (4..8..2); do echo $i; done 4 6 8 Counting is possible both for increment and decrement of values: $ for i in (6..-4..3); do echo $i; done 6 3 0 -3

5. Parameter substitution ( in "$@")

Executes commands for each parameter that was passed to the script. for VAR in $@ do done or in one line: for VAR in $@; do ; done
So if you create a script test.sh #!/bin/sh for VAR in $@ do echo$VAR done then when you run it with the parameters: $ ./test.sh param1 param2 param3 param1 param2 param3 Part in$@ can be omitted. Then the test.sh script will be rewritten: #!/bin/sh for VAR do echo$VAR done
Let me give you a couple of examples (with in and without): $ function FUNC_1 ( for VAR in $@; do echo $VAR; done; ) $ FUNC_1 param1 param2 param3 param1 param2 param3 $ function FUNC_2 ( for VAR; do echo $VAR; done; ) $ FUNC_2 param1 param2 param3 param1 param2 param3

6. Using continue and break in a for loop

For all of the above constructs, it is possible to use the commands "continue" to move to the next element of the loop or "break" to exit the loop.

Example (complete when i=6 and don't execute when i=3 and i=5): for i in (1..8); do if[ $i -eq 6 ]; then break; fi if[ $i -eq 3 ] || [ $i -eq 5 ]; then continue; fi echo $i done Execution result: $ $ for i in (1..8); do \ > if [ $i -eq 6 ]; then break; fi; \ > if [ $i -eq 3 ] || [ $i -eq 5 ]; then continue; fi; \ > echo $i; \ > done 1 2 4

  • Tutorial

BASH Basics. Part 2.
I apologize for such a long delay between articles, but the session makes itself felt at the most inopportune moment :)
Thank you all for the comments, criticism and additions that were voiced in the comments to the last article.
This part, as promised, will be devoted to loops, mathematical operations and the use of external commands.
Let's begin.

Cycles. For-in loop.

The for-in operator is intended for sequential access to the values ​​listed in the list. Each value in turn in the list is assigned to a variable.
The syntax is as follows:
for variable in value_list
do
teams
done

Let's consider small example:

#!/bin/bash
for i in 0 1 2 3 4 #we will alternately assign values ​​from 0 to 4 inclusive to the variable $i
do
echo "Console number is $i" >> /dev/pts/$i #Write the line "Console number is $i" to the file /dev/pts/$i (virtual terminal file)
done #cycle finished
exit 0

After executing the example, a line with its number will appear in the first 5 virtual consoles (terminals). Values ​​from the list are alternately substituted into the variable $i and the value of this variable is processed in a loop.

Cycles. While loop.

The while loop is more complex than the for-in loop and is used to repeat commands as long as some expression is true (return code = 0).
The operator syntax is as follows:
while expression or command returning a return code
do
teams
done

Let's look at the following example of how the loop works:

#!/bin/bash
again=yes #assign the value "yes" to the variable again
while [ "$again" = "yes" ] #We will execute the loop until $again is equal to "yes"
do
echo "Please enter a name:"
read name
echo "The name you entered is $name"

Echo "Do you wish to continue?"
read again
done
echo "Bye-Bye"


And now the result of the script:
ite@ite-desktop:~$ ./bash2_primer1.sh
Please enter a name:
ite
The name you entered is ite
Do you wish to continue?
yes
Please enter a name:
mihail
The name you entered is mihail
Do you wish to continue?
no
Bye-Bye

As you can see, the loop runs until we enter something other than “yes”. Between do and done you can describe any structures, operators, etc., all of them will be executed in a loop. But you should be careful with this loop, if you run any command in it without changing the expression variable, you can get caught in an endless loop.
Now about the truth condition. After the while, as in the if-then-else conditional statement, you can insert any expression or command that returns the return code, and the loop will be executed until the return code = 0! The "[" operator is analogous to the test command, which checks the truth of the condition that was passed to it.

Let's look at another example, I took it from the book Advanced Bash Scripting. I really liked it :), but I simplified it a little. In this example we will introduce another type of UNTIL-DO loops. This is almost a complete analogue of the WHILE-DO loop, only it is executed while some expression is false.
Here's an example:

#!/bin/bash
echo "Enter numerator: "
read dividend
echo "Enter denominator: "
read divisor

Dnd=$dividend #we will change the variables dividend and divisor,
#let's save their knowledge in other variables, because... they give us
#will be needed
dvs=$divisor
remainder=1

Until [ "$remainder" -eq 0 ]
do
let "remainder = dividend % divisor"
dividend=$divisor
divisor=$remainder
done

Echo "GCD of the numbers $dnd and $dvs = $dividend"


Result of script execution:
ite@ite-desktop:~$ ./bash2_primer3.sh
Enter the numerator:
100
Enter the denominator:
90
GCD of numbers 100 and 90 = 10

Mathematical operations

let command.
The let command produces arithmetic operations over numbers and variables.
Let's look at a small example in which we perform some calculations on the entered numbers:
#!/bin/bash
echo "Enter a: "
read a
echo "Enter b: "
read b

Let "c = a + b" #addition
echo "a+b=$c"
let "c = a / b" #division
echo "a/b=$c"
let "c<<= 2" #сдвигает c на 2 разряда влево
echo "c after shift by 2 bits: $c"
let "c = a % b" # finds the remainder of a divided by b
echo "$a / $b. remainder: $c "


Execution result:
ite@ite-desktop:~$ ./bash2_primer2.sh
Enter a:
123
Enter b:
12
a+b= 135
a/b= 10
c after shift by 2 digits: 40
123 / 12. balance: 3

Well, as you can see, there is nothing complicated, the list of mathematical operations is standard:
+ - addition
- - subtraction
* - multiplication
/ - division
** - exponentiation
% - modulus (modulo division), remainder of division
let allows you to use abbreviations for arithmetic instructions, thereby reducing the number of variables used. For example: a = a+b is equivalent to a +=b, etc.

Working with external programs when writing shell scripts

First, some useful theory.
Stream redirection.
Bash (like many other shells) has built-in file descriptors: 0 (stdin), 1 (stdout), 2 (stderr).
stdout - Standard output. Everything that programs output goes here
stdin - Standard input. This is all that the user types in the console
stderr - Standard error output.
For operations with these handles, there are special characters: > (output redirection),< (перенаправление ввода). Оперировать ими не сложно. Например:
redirect the output of the cat /dev/random command to /dev/null (absolutely useless operation :))) or
write the contents of the current directory to the listing file (more useful)
If there is a need to append to a file (when using ">" it will be replaced), you must use ">>" instead of ">"
after asking sudo for the password, it will be taken from the my_password file, as if you had entered it from the keyboard.
If you need to write to a file only errors that could occur while running the program, you can use:
./program_with_error 2> error_file
the number 2 before ">" means that you need to redirect everything that ends up in descriptor 2 (stderr).
If you need to force stderr to write to stdout, then this can be done as follows. way:
the symbol "&" means a pointer to descriptor 1(stdout)
(By default, stderr writes to the console in which the user is working (or rather writes to the display)).
2. Conveyors.
The pipeline is a very powerful tool for working with the Bash console. The syntax is simple:
team1 | command 2 - means that the output of command 1 will be passed as input to command 2
Pipelines can be grouped into chains and output using redirection to a file, for example:
ls -la | grep "hash" |sort > sortilg_list
The output of the ls -la command is passed to the grep command, which selects all lines containing the word hash and passes it to the sort command, which writes the result to the sorting_list file. Everything is quite clear and simple.

Most often, Bash scripts are used to automate some routine operations in the console, hence sometimes there is a need to process the stdout of one command and transfer it to stdin to another command, while the result of executing one command must be processed in some way. In this section I will try to explain the basic principles of working with external teams inside the script. I think that I have given enough examples and now I can write only the main points.

1. Passing the output to a variable.
In order to record the output of a command into a variable, it is enough to enclose the command in `` quotes, for example
a = `echo "qwerty"`
echo $a

Result: qwerty


However, if you want to store a list of directories in a variable, you must properly process the result to place the data in the variable. Let's look at a small example:
LIST=`find /svn/ -type d 2>/dev/null| awk "(FS="/") (print $4)"| sort|uniq | tr "\n" " "`
for ONE_OF_LIST in $LIST
do
svnadmin hotcopy /svn/$ONE_OF_LIST /svn/temp4backup/$ONE_OF_LIST
done

Here we use a for-do-done loop to archive all directories in the /svn/ folder using the svnadmin hotcopy command (which in our case does not matter, just as an example). The line of greatest interest is: LIST=`find /svn/ -type d 2>/dev/null| awk "(FS="/") (print $4)"| sort|uniq | tr "\n" " "` In it, the LIST variable is assigned the execution of the find command, processed by the awk, sort, uniq, tr commands (we will not consider all these commands, because this is a separate article). The LIST variable will contain the names of all directories in the /svn/ folder placed on one line (in order to feed it into the cycle.

As you can see, everything is not difficult, just understand the principle and write a couple of your own scripts. In conclusion of the article, I would like to wish you good luck in learning BASH and Linux in general. Criticism, as usual, is welcome. The next article may be devoted to the use of programs such as sed, awk.

What is a shell and why is it needed?

The command shell in any unix-like systems, which includes GNU/Linux, is a regular program launched both in a text console (which is used less and less) and in a graphical environment - in the window of a terminal emulator, available on any Linux system .

Its task is simple and obvious: accept a line (or lines) of input, parse them and, based on the results of this analysis, react accordingly - execute a command, run a program, display a diagnostic message, etc.

Almost all Linux distributions For users, the default command shell is bash (Bourne Again SHell is another Bourne command shell; Steve Bourne is the author of the first command shell in Unix - sh). In fact, it has become an unofficial standard, and improvements to it functionality continues continuously. There are other command shells - tcsh (version of C-shell), ksh (Korn Shell), zsh, etc. – each has its own advantages and disadvantages, as well as its own groups of fans. However, bash is more familiar to a wide range of users with different levels of experience, which is why I chose it. It is also worth noting that no matter what capabilities the various shells have, they are all compatible with their ideological progenitor - the Bourn Shell (sh). In other words, a script written for sh will work correctly in any modern shell (the reverse is generally not true).

Benefits of the Command Line

The question may arise: why bother with the command line if there are convenient and beautiful graphical interfaces? There are many reasons for this. Firstly, not all operations are more convenient or faster to perform using GUI. Second, every program follows the fundamental principle of Unix systems: do a well-defined job and do it well. In other words, you always understand what happens when you run a particular utility (if something is not entirely clear, you should refer to the man manual). Thirdly, by mastering commands, trying their combinations and combinations of their parameters, the user studies the system, acquiring valuable practical experience. You get access to powerful tools such as pipelines that allow you to organize a chain of commands for processing data, I/O redirection tools, and you can also program directly from the command shell. Perhaps it’s worth dwelling on programming in more detail, especially since many system scripts in Linux (for example, scripts for launching system services) are written for the shell.

Command shell as a programming language

So, the command shell can be considered as a programming language and as software environment execution simultaneously. Of course, this language is not compiled, but interpreted. It allows the use of variables: system or own. The sequence of execution of program commands is changed using condition checking constructs and selecting the appropriate option: if-then-else and case. While, until, and for loops allow you to automate repetitive actions. It is possible to combine groups of commands into logical blocks. You can even write real functions that pass parameters to them. Thus, all the signs and characteristics of a full-fledged programming language are available. Let's try to get double benefit from this - along with learning the basics of programming, we will automate our daily work.

Hello, World! Simple backup system

About the need for regular Reserve copy Everyone knows the data, but users never have enough time for this boring operation. The solution is simple - organize automatic creation of backup copies. This will be our first shell programming task.

#!/bin/bash # # Backup directories and files from the home directory # This batch script can be run automatically using cron # cd $HOME if [ ! -d archives ] then mkdir archives fi cur_date=`date +%Y%m%d%H%M` if [ $# -eq 0 ] ; then tar czf archive$(cur_date).tar.gz projects bin else tar czf archive$(cur_date).tar.gz $* fi if [ $? = 0 ] ; then mv archive$(cur_date).tar.gz $HOME/archives echo "$cur_date – Backup completed successfully." else echo "$cur_date - ERROR during backup." fi

Any command script (script is a script, as command shell programs are called) begins with an identifier line, in which the command interpreter is explicitly specified, indicating the full path to it. The full path is a sequential listing of all directories, starting from the root, that must be entered to get to the target file, and, of course, the name of this file. Recording the full path is extremely important to uniquely identify each file in the file system hierarchy.

Four lines of comments follow. Once the shell encounters the "#" character, it treats all subsequent characters as comments and completely ignores them until the end of the current line. Therefore, you can start a comment not from the very beginning of the line, but accompany it with some command.

After the comments there is an empty line. It means nothing to the command shell, and no action is taken. In scripts, blank lines are usually inserted to make the code easier to read.

We finally got to the first “real” team. It allows you to change the directory (Change Directory), i.e. move from the current directory to another one passed to the command as an argument. In most cases, the target directory is specified explicitly, for example, cd /tmp or cd projects, but in our case the predefined system variable HOME is used - it contains the full path to the home directory current user, on behalf of which the command script is executed. This eliminates the need to make code changes every time we change users, because the command returns everyone to their personal directory. The dollar sign "$" in front of a variable name means that you need to extract the value contained in that variable and substitute it in place of its name on the command line. It should be especially noted that in the command language, letter case shells have important, i.e. HOME, Home and home are three different variables. By agreement, in letters uppercase The names of system variables are indicated: HOME, PATH, EDITOR, etc. This convention does not prevent users from creating their own variables with names from capital letters, but why complicate your life by violating generally accepted norms and rules? It is also not recommended to change the values ​​of system variables unless absolutely necessary. In general, we follow a simple rule: we use system variables for read-only purposes, and if we need our own, we write its name in lowercase letters.

Our first command could be written more briefly:

cd ~

Here the "~" symbol also means the current user's home directory. Command line veterans put it even more succinctly:

CD

The idea is that when the cd command is given no argument, it changes to the home directory.

Next up is the classic software design for checking conditions and making the appropriate decision. The general scheme is:

if<условие>then<одна или несколько команд>fi

The last word of the construction (if in reverse order) acts as a closing parenthesis, i.e. boundaries of the list of commands executed when the condition is true. The presence of fi is mandatory, even if there is only one team on the list.

To check a condition, as a rule, the test command or its alternative form of notation in square brackets is used. In other words, records

if [! -d archives ] if test ! -d archives

absolutely equivalent. I prefer square brackets because they more clearly define the boundaries of the condition being tested. Both the right and left parentheses must be separated from the condition by spaces.

The criteria for checking the condition are determined by various flags. The test command recognizes a very large list of them. In our example, the -d flag is used, which allows us to check whether the name specified after the flag corresponds to an actual directory. The following flags are most often used when working with files:

F – whether a regular file with the given name exists;

R – whether the specified file has the right to read from it;

W – whether the specified file has the right to write to it;

X – whether the specified file has the right to execute it;

S – whether the specified file has a non-zero size.

In our case, the condition is preceded by Exclamation point, denoting the operation of logical negation, so the meaning of the condition being tested becomes completely opposite. Let's try to write down the meaning of these commands in ordinary Russian:

if [! -d archives ] If the archives directory (in the current directory) does not exist, then start executing the command block: mkdir archives create the archives directory (in the current directory) fi end executing the command block.

As you can see, everything turned out to be not so complicated. With a little practice, you can easily read and create similar designs yourself. The directory creation command is so obvious that no further explanation is required.

On the next line we create our own local variable, cur_date. In the vast majority of cases, variables are created by simply assigning a specific value, for example:

ten=10 string="This is a line of text"

But in our example, a little trick is used. Please note that after the equal sign - the assignment symbol - the command is written in back quotes. This form of notation allows you to assign to a variable not the string itself, but the result of its execution. Here is the output of the date command, which returns the current date and time in a format specified by a list of parameters:

%Y – current year in full form, i.e. of four digits (for example, 2009);

%m – number of the current month (for example, 09 – for September);

%d – current day number;

%H – current hour in 24-hour format;

%M – current minute.

Thus, if you run the command

cur_date=`date +%Y%m%d%H%M`

on the tenth of September 2009 at 22:45, the variable cur_date will be assigned the string value "200909102245". The purpose of this trick is to create a unique, non-repeating name for the archive file. If you intend to run several instances of the program within one minute, you can improve the uniqueness of the names by adding the current seconds. How? Study the date utility manual (man date) - there is nothing complicated about it.

Before we start creating an archive file, we need to determine which directories we will save in it. For greater flexibility, we can specify a set of directories to be archived by default, but provide the ability to replace this set with a list of directories passed as an argument to our command script. For this purpose, special command shell variables are used: $# – the number of parameters passed to the script and $* – all parameters passed, written in one line format.

if [ $# -eq 0 ] ; then

Checking the condition “if the number of passed parameters is zero”, then execute the following command. Note that the then keyword can be written on the condition line, separated from the conditional expression by a semicolon.

tar czf archive$(cur_date).tar.gz projects bin

The command to create an archive file and compress this file. The tar utility itself does not perform compression, but only collects everything specified files and directories into a single tar file. The first flag is intended for this - c (create). Compression performs external program– here it is gzip, called by the second flag - z. If you have the more efficient bzip2 compression program installed on your system, you can take advantage of it by modifying the command as follows:

tar cjf archive$(cur_date).tar.bz2 projects bin

The third flag f indicates that what follows is the name of the archive file, so it is always the last one in the list of flags. Note that when substituting, the variable name is enclosed in curly braces. This is done to explicitly highlight the variable on the line surrounding it, thereby eliminating many potential problems. Extensions archive file are not assigned automatically; you fill in everything you need yourself. I've specified projects and bin as the default directories to archive, but you can write down the names of your most valuable directories here.

The else keyword opens an alternative branch of execution. The commands of this block begin to work if the condition check returns the result “false” (in our example: “the number of parameters passed is non-zero,” i.e. the user specified directory names). In this case the command will look like this:

tar czf archive$(cur_date).tar.gz $*

Here the default directories are replaced by a directory name string accepted externally. It is possible to accept and process each external parameter separately, but it is more convenient for us to pass the entire string.

At the end of the program, another check is performed. In Unix environments, all commands return a completion status code. If the command was successful, it returns code 0, otherwise the exit code will be non-zero. To check the success of the previous archiving command, we will use another special variable $?, which always contains the value of the completion code of the most recent command. If in the variable $? contains 0, i.e. The backup file was successfully created, then we move it to the archive directory:

mv archive$(cur_date).tar.gz $HOME/archives

and display the corresponding message:

echo "$cur_date – Backup completed successfully."

If the check shows that the completion code of the archiving operation is not zero, then an error message is displayed:

echo "$cur_date - ERROR during backup."

This completes our command script.

To check the operation of our program, you need to save the above source in a file called bckp, for example, and then make it executable for convenience:

chmod 750 bckp

and run:

./bckp

to create a backup of the default directories, and

./bckp docs progs works

to create a backup copy of the listed directories (specify the names of the directories that actually exist on your system, otherwise you will receive an error message).

You can place the bckp file in one of the directories specified in the system PATH variable. The most preferred locations are /usr/local/bin or $HOME/bin if you have them. After this you can run bckp as a system command.

How to automate scheduled backup operations

A few words about backup automation. For this purpose, the system serves cron scheduler, which reads work instructions from a special crontab file. To define such instructions, you need to create and edit your crontab file using the command:

crontab -e

Instructions are written in a strictly defined format (fields are separated by spaces):

minutes hours day_of_month month day_of_week command

One option for scheduling backup operations might look like this:

30 23 10,20,30 * * /usr/local/bin/bckp

This means that the backup script (you must provide the full path to this file) will run at 23:30 on the 10th, 20th and 30th of each month, regardless of the day of the week. (Asterisks indicate the entire permissible range of values, in this case: every month - in the 4th field, any day of the week - in the 5th field)

If you prefer to summarize your results by week, and your system runs 24/7, then it makes sense to schedule backups during off-peak hours:

0 5 * * 3.5 /usr/local/bin/bckp

Here backups will be created at 5:00 on Wednesdays and Fridays in each month (asterisk in the 4th field), regardless of the date (asterisk in the 3rd field).

You can read about all the intricacies of scheduling in man manual 5 crontab.

Results and conclusions

The backup script discussed in this article has modest functional properties. But that was not his point the main task, but so that the reader understands what can be done on the command line, and not only copies and executes the proposed command file, but becomes interested in expanding its functions and begins to explore the immense possibilities provided by command shells. And if someone, after reading this article, tries to improve the code given here, or writes their own version, or implements their own independent idea, then I will consider that the main goal has been achieved.

Resources for download

static.content.url=http://www.site/developerworks/js/artrating/

ArticleID=458335

ArticleTitle=Shell Programming Basics

The shell programming language has several constructs that will give flexibility to your programs:

  • Commentaries will allow you to describe the functions of the program;
  • "here document" allows you to include lines in the program's shell that will be redirected as input to some of the program's shell commands;
  • The exit command allows you to terminate the program at the desired point and use return codes;
  • The for and while loop constructs allow you to repeat a group of commands in a loop;
  • conditional commands if and case execute a group of commands if some condition is met;
  • The break command allows you to unconditionally exit a loop.

9.3.1. Comments

To place comments in the program, use the # sign. If a # sign appears after a command, the command itself is executed and the comment is ignored. Comment line format:

#comment

9.3.2. "Here document"

"Here document" allows you to place lines in a shell program that are redirected as command input in that program. This is one way to provide input for a command in a shell program without using separate file. The entry consists of a redirection character<< и разделителя, который указывает начало и конец строк ввода. В качестве разделителя может использоваться один символ или строка символов. Чаще всего это знак!.

The command format is as follows:

Command<...input lines... delimiter

9.3.3. Using ed in a shell program

"Here document" suggests a way to use ed in a shell program. Let's say you want to create shell program, which will call the ed editor, make global changes to the file, write the changes to the file, and then exit ed. The following screen shows the contents of the ch.text program, which performs these tasks:

$ cat ch.text echo Type in the filename read file1 echo Type in the exact text to be changed. read old_text echo Type in the exact new text to replace the above. read new_text ed - $file1<

Note the - (minus) sign in the ed command. This option prevents the character counter from being printed on the screen. Also note the format of the ed command for global replacement:

G/$old_text/s//$new_text/g

The program uses 3 variables: file1, old_text, new_text. When run, this program uses the read command to obtain the values ​​of these variables. These variables contain the following information:
file - name of the file that will be edited;
old_text - text that will be changed;
new_text - new text.

Variables are introduced into the program, here document redirects the global replace command, the write command, and the end command to the ed command. Run the ch.text program. Get the following screen:

$ch.text Type in the filename memo Type in the exact text to be changed. Dear John: Type in the exact new text to replace the above. To what it may concern: $ cat memo To what it may concern: $

9.3.4. Completion Codes

Most shell commands return codes that indicate whether the command completed successfully. If the return value is 0 (zero), then the command completed successfully. Return codes are not printed automatically, but can be obtained as the value of the special shell $? parameter.

9.3.4.1. Checking completion codes

After running a command interactively, you can see the exit code when you type:

Echo$? Consider the following example: $ cat hi This is file hi. $echo$? 0 $ cat hello cat: cannot open hello $ echo $? $2

In the first case, the file hi exists in your directory and you have read permission. Using the cat command you can print the contents of a file. The result of the cat command is the return code 0, which you will get by specifying the $? parameter. In the second case, the file either does not exist or you do not have read permission. The cat command prints a diagnostic message and returns code 2.

The shell program exits normally when the last command in the file is executed. However, you can use the exit command to terminate the program. More importantly, you can use the exit command to obtain shell program return codes.

9.3.5. Cycles

The for and while loop statements allow you to execute a command or sequence of commands multiple times.

9.3.5.1. for statement

The for statement executes a sequence of commands for each element of the list. It has the format:

For variable in a_list_of_values do command_1 command_2 . . . last command done

For each iteration of the loop, the next element of the list is assigned to the variable given in the for statement. This variable can be referenced anywhere in commands within a do statement. When constructing each section of commands, you need to make sure that for each do there is a corresponding done at the end of the loop.

The variable can have any name. For example, if your variable is named var, then a reference to $var in the command list will make the value available. If the in operator is omitted, then the value for var will be the set of arguments given in the command and available in the special parameter $*. The list of commands between the do and done keywords will be executed for each value.

When the commands are executed for last element list, the program will execute the line below done.

9.3.5.2. while statement

The while loop operator uses 2 groups of commands. It will execute the sequence of commands in the second group (the do ... done list) until the last command in the first group (the while list) returns true, meaning that the expression after the do can be executed.

General format while loop operator:

While command_1 . . . last command do command_1 . . . last command done

For example, the enter.name program uses a while loop to enter a list of names into a file. The program consists of the following command lines:

$ cat enter.name while read x do echo $x>>xfile done $

After making some additions, we get the following program:

$ cat enter.name echo Please type in each person"s name and than a echo Please end the list of names with a<^d>while read x do echo $x>>xfile done echo xfile contains the following names: cat xfile $

Note that after the loop completes, the program executes the commands below done.

The first two echo commands use special characters, so you must use quotes to escape the special meaning. The following screen shows the output of enter.name:

$enter.name Please type in each person"s name and than a Please end the list of names with a<^d>Mary Lou Janice <^d>xfile contains the following names: Mary Lou Janice $

After the loop completes, the program will print out all the names contained in the xfile.

9.3.6. Using /dev/null

The file system has a file /dev/null where you can store unwanted output. For example, if you simply enter the who command, the system will answer who is working on the system. If you redirect the output of this command to /dev/null:

Who > /dev/null then you will not get a response.

9.3.7. Conditional statements

if...then statement

The if command tells the shell program to execute the sequence of commands after then if the last command in the if statement's list of commands completed successfully. If constructs end with the fi keyword.

The general format of the if construct is:

If command_1 . . . last command then command_1 . . . last command fi

For example, the search shell program demonstrates the use of the if ... then construct. The search program uses the grep command to search for a word in a file. If grep is successful, the program displays the word found. The screen will look like this:

$ cat search echo Type in the word and the file name. read word file if grep $word $file then echo $word is in $file fi $

This program displays the output of the grep command. If you want to store the system's response to the grep command in your program, use the /dev/null file, changing the if command line to the following:

If grep $word $file > /dev/null

Now run the search command. It will only respond with the message specified after the echo command.

The if ... then ... else construct can execute an alternative set of commands after the else if the if sequence is false. The format of this construct is as follows:

If command_1 . . . last command .linthen command_1 . . . last command else command_1 . . . last command fi

With this construct, you can improve the search program so that it will tell you both the word found and the word not found. In this case, the search program will look like this:

$ cat search echo Type in the word and the file name. read word file if grep $word $file > /dev/null then echo $word is in $file else echo $word is NOT in $file fi $

test command

The test command is used to organize a loop. It tests certain conditions for truth and is useful for organizing conditional structures. If the condition is true, the loop will continue. If the condition is false, the loop will end and the next command will be executed. Some examples of using the test command: test -r file true if the file exists and is readable; test -w file true if the file exists and is writable; test -x file true if the file exists and is executable; test -s file true if the file exists and has at least one character; test var1 -eq var2 true if var1 is equal to var2; test var1 -ne var2 true if var1 is not equal to var2.

Example. Let's create a shell program that moves all executable files from the current directory to your bin directory. To do this, we will use the test -x command to select executable files. The mv.file program will look like this:

$ cat mv.file echo type in the directory path read path for file do if test -x $file then mv $file $path/$file fi done $

The case ... esac construct allows you to select one of several patterns and then execute a list of commands for that pattern. The template expression must begin with the in keyword and a right parenthesis must be placed after last character each template. The sequence of commands for each pattern ends with two characters;;. The case construct must be terminated with the esac keyword.

The general format of the case construct is:

Case word in pattern1) command line 1 . . . last command line ;;pattern2) command line 1 . . last command line ;;pattern3) command line 1 . . last command line ;; *)command line 1 . . last command line ;;esac

The case construct tries to find a word with the pattern pattern in the first pattern section. If the search is successful, the program executes command lines after the first pattern before the corresponding signs;;.

If the first template is not found, then the transition to the second template is carried out. If any pattern is found, the program does not consider the remaining patterns, but moves on to the command following esac. The * is used as a pattern to search for any word and thus gives you a set of commands that will be executed if no other pattern is found. Therefore, the asterisk (*) pattern is placed as the last pattern in the case construct so that the other patterns are checked first. This will help you detect incorrect and unexpected input.

Templates can use metacharacters *, ?, . This provides program flexibility.

Let's look at an example. The set.term program sets the TERM variable according to the type of terminal you are using. The following command line is used:

TERM=terminal_name

The * template is the last one in the list of templates. It issues a warning message that there is no matching pattern for the specified terminal type and allows you to complete the case construct.

$ cat set.term echo If you have a TTY 4420 type in 4420 echo If you have a TTY 5410 type in 5410 echo If you have a TTY 5420 type in 5420 read term case term in 4420) TERM-T4 ;; 5410) TERM-T5 ;; 5420) TERM-T7 ;; *) echo not a correcr terminal type ;; esac export TERM echo end of programm $

9.3.8. Unconditional transfer of control

The break command unconditionally stops the execution of any loop in which it is encountered and passes control to the command following keywords done, fi or esac.

In the previous example set.term program, you could use the break command instead of echo to exit the program, as in the following example:

$ cat set.term echo If you have a TTY 4420 type in 4420 echo If you have a TTY 5410 type in 5410 echo If you have a TTY 5420 type in 5420 read term case term in 4420) TERM-T4 ;; 5410) TERM-T5 ;; 5420) TERM-T7 ;; *) break ;; esac export TERM echo end of programm $

The continue command will cause the program to immediately move to the next iteration of the while or for loop without executing the remaining commands in the loop.