📜 ⬆️ ⬇️

Scripting Techniques in Bash

Linux administrators have to write bash scripts regularly. Below I provide tips on how to speed up this work, as well as improve the reliability of scripts.

Tip 1

Do not write scripts that perform actions without asking anything. Such scripts are rarely needed. But all kinds of "good" for copying, synchronization, launching something, more than enough. And if in your favorite Midnight Commander you suddenly clicked on the wrong script, then anything can happen to the system. It's like the rules of the road - "written in blood."

Tip 2

Starting from the previous one, it’s nice to put something like: at the beginning of each script:
read -n 1 -p " ,     (y/[a]): " AMSURE [ "$AMSURE" = "y" ] || exit echo "" 1>&2 
The echo command, by the way, is needed here because after pressing the <y> button you will not have a line feed, therefore, the next output will go to the same line.

Tip 3

This is the key advice of all. In order not to write the same thing every time - use libraries of functions. After reading a lot of articles on Bash, I have to admit that this topic receives little attention. Perhaps because of evidence. However, I consider it necessary to recall this. So.
Get your own library of functions, for example myfunc.sh, and put it, for example, in / usr / bin. When writing scripts, it will not only help reduce your work, but also allow you to modify many scripts in one fell swoop if you improve any function.
For example, in the light of Tip 2, you can write this function:
 myAskYN() { local AMSURE if [ -n "$1" ] ; then read -n 1 -p "$1 (y/[a]): " AMSURE else read -n 1 AMSURE fi echo "" 1>&2 if [ "$AMSURE" = "y" ] ; then return 0 else return 1 fi } 
The only optional parameter this function takes is the question string. If the string is not set - silent wait for pressing (in cases where the script has already managed to display all that is needed before calling this function). Thus, the application may be such:
 myAskYN " ,    ?" || exit 
You can write another similar function myAskYNE, with the letter E at the end, in which return is replaced with exit. Then the recording will be even easier:
 myAskYNE " ,    ?" 
The advantages are obvious: a) write less code, b) code is easier to read, c) do not get distracted by trifles, like the prefix "(y / [a]):" to the test (I note that [a] means any, and taken into square quotes indicates that this is the default).
And the last is here. In order to use functions from our library, you need to remember to include it in the script itself:
 #!/bin/bash a1=myfunc.sh ; source "$a1" ; if [ $? -ne 0 ] ; then echo " —    $a1" 1>&2 ; exit 1 ; fi myAskYN " ,    ?" echo Run! 
I deliberately put the whole call and error handling in one line, because this thing is standard and does not relate directly to the logic of the script. Why stretch it half a screen? Note also that the name of the script is assigned to a variable. This allows you to specify the name of the script once, and therefore, you can duplicate the string and replace the library name to connect another library of functions, if necessary.
Now, any script starting with these three lines will never do something without confirmation. I grant you to write a function similar to myAskYN, called myAskYESNO.

Tip 4

Develop success and demonstrate some obvious features with minimal comments.
 sayWait() { local AMSURE [ -n "$1" ] && echo "$@" 1>&2 read -n 1 -p "(    )" AMSURE echo "" 1>&2 } cdAndCheck() { cd "$1" if ! [ "$(pwd)" = "$1" ] ; then echo "!!     $1 -  . ." 1>&2 exit 1 fi } checkDir() { if ! [ -d "$1" ] ; then if [ -z "$2" ] ; then echo "!!  $1 -  . ." 1>&2 else echo "$2" 1>&2 fi exit 1 fi } checkFile() { if ! [ -f "$1" ] ; then if [ -z "$2" ] ; then echo "!!  $1 -  . ." 1>&2 else echo "$2" 1>&2 fi exit 1 fi } checkParm() { if [ -z "$1" ] ; then echo "!!$2.  . ." 1>&2 exit 1 fi } 
Here I will draw your attention to the constantly occurring combination 1> & 2 after echo. The fact is that your scripts will probably display some valuable information. And this information does not always fit into the screen, and therefore it is not bad to save it to a file or send it to less. The combination 1> & 2 means redirecting output to the standard error device. And when you call the script like this:
 my-script.sh > out.txt my-script.sh | less 
there will be no unnecessary erroneous and service messages, but only what you really want to display.

Tip 5

Bash does not do well with returning a value from a function. However, using your own library, this issue is easily solved. Just get a variable in which the function will enter the value, and on exiting the function analyze this variable. By the way, the declaration of a variable is well placed at the beginning of the library of your functions. Also, you can create other variables that will be used everywhere. Here is the beginning of your library of functions:
 curPath= #     ,    cRes= #        pYes= #  --yes,    
Now we can add another useful function to the collection:
 input1() { local a1 if [ -n "$1" ] ; then read -p "$1" -sn 1 cRes else read -sn 1 cRes fi #    while [ "$2" = "${2#*$cRes}" ] ; do read -sn 1 cRes done echo $cRes 1>&2 } 
Here is an example of its use:
 cat <<'EOF'   : ------------------------ a)  1 b)  2 .)  EOF input1 " : " "ab." echo " : $cRes" 
This function restricts keystrokes to the list of specified ones (for example, a, b, and dot). No other keys will be perceived and nothing will be displayed when they are pressed. The example also shows the use of a return variable ($ cRes). It returns the letter pressed by the user.

Tip 6

What script without parameters? About their processing written tons of literature. I will share my vision.
  1. It is highly desirable that the parameters be processed regardless of their sequence.
  2. I do not like to use single-letter parameters (and therefore getopts) for the simple reason that there are a lot of scripts and few letters. And remember that for one script -r means replace, for another replicate, and for the third, remove is almost impossible. Therefore, I use 2 notations, and at the same time: a) - show-files-only, b) -sfo (as a contraction from the previous one). Practice shows that such keys are remembered instantly and for a very long time.
  3. The script should give an error to an unknown key. This will partially help to identify errors when writing parameters.
  4. From council 2 we take the rule: never run a script without confirmation. But we add to this an important exception - if the --yes key is not specified (the key, of course, can be any).
  5. Keys may be accompanied by a value. In this case, the following rule applies for long keys: --source-file = my.txt (spelling through equals), and for short ones: -sf my.txt (separated by spaces).
In this light, the processing parameters may look like this:
 while [ 1 ] ; do if [ "$1" = "--yes" ] ; then pYes=1 elif [ "${1#--source-file=}" != "$1" ] ; then pSourceFile="${1#--source-file=}" elif [ "$1" = "-sf" ] ; then shift ; pSourceFile="$1" elif [ "${1#--dest-file=}" != "$1" ] ; then pDestFile="${1#--dest-file=}" elif [ "$1" = "-df" ] ; then shift ; pDestFile="$1" elif [ -z "$1" ] ; then break #   else echo ":  " 1>&2 exit 1 fi shift done checkParm "$pSourceFile" "   " checkParm "$pDestFile" "   " if [ "$pYes" != "1" ] ; then myAskYNE " ,    ?" fi echo "source=$pSourceFile, destination=$pDestFile" 
This code provides the following features:This is the basic part that can be developed further. For example, add a couple of parameter processing functions to our library:
 procParmS() { [ -z "$2" ] && return 1 if [ "$1" = "$2" ] ; then cRes="$3" return 0 fi return 1 } procParmL() { [ -z "$1" ] && return 1 if [ "${2#$1=}" != "$2" ] ; then cRes="${2#$1=}" return 0 fi return 1 } 
At the same time, the parameter processing cycle will look much more digestible:
 while [ 1 ] ; do if [ "$1" = "--yes" ] ; then pYes=1 elif procParmS "-sf" "$1" "$2" ; then pSourceFile="$cRes" ; shift elif procParmL "--source-file" "$1" ; then pSourceFile="$cRes" elif procParmS "-df" "$1" "$2" ; then pDestFile="$cRes" ; shift elif procParmL "--dest-file" "$1" ; then pDestFile="$cRes" elif [ -z "$1" ] ; then break #   else echo ":  " 1>&2 exit 1 fi shift done 
In fact, this cycle can be copied from the script to the script without thinking about anything other than the names of the keys and the name of the variable for this key. And in this case they are not repeated and the possibility of error is excluded.
There is no limit to perfection, and you can “improve” functions for a long time, for example, in procParmS, check the third parameter for a non-empty value and fall out by mistake in such a case. And so on.
The function library file from this example can be downloaded here .
Test file here .

')

Source: https://habr.com/ru/post/158971/


All Articles