BASH recursion examples - part 2

BASH recursion examples - part 2

Created:30 Jan 2017 17:08:29 , in  Host development

Asking for permission to carry on

Here is the first example of recursive function.

Imagine your script needs to ask for permission to proceed to the next stage of some automation process. Perhaps, that next stage consists of deletion of some important directories and files. You need to get clear yes (y), no (n) or abort (q) from the user before the scripts moves on.

Below is a function that asks for permission to continue. It reads user input and unless it gets y, n or q ( termination condition ) it calls itself again. Entering y as an answer to a question the function asks will set $? to 0, the other two valid answers will set it to 1 (More elaborate code could also distinguish between n and q).


can_begin() {
  recursive
  local answer=${answer:-''}
  local regex='^(y|n|q)$'
  echo "Do you want to begin now ? [y/n/q]" 
  read -r answer
  [[ $answer =~ $regex ]] && {
    [[ $answer == 'y' ]] && {
      return
    } || {
      return 1
    }
  }
  can_begin
}

Run it:


if can_begin; then
  echo 'Beginning ...'
else
  echo 'Aborting ...'
fi

Installing dependencies

A recursive function that installs programs (unless they are on the system already) with names stored in array called 'dependencies'. Internally, it uses apt-get utility to that ( necessary privileged access ).


dependencies=( rsync unzip gpg )

install_dependencies(){
  recursive
  local -i i="${i:-${#dependencies[@]}}"
  (( i <= 0 )) && {
    return
  } 
   
  [[ -z $( which "${dependencies[$i-1]}" ) ]] && {
    echo "${dependencies[$i-1]} needs to be installed. Installing ..."
    apt-get install -y "${dependencies[$i-1]}"
  } || {
    echo "${dependencies[$i-1]} installed already."
  }
  ((i--))
  install_dependencies
} 

Call it:


install_dependencies

Extending directory name

Suppose, you need a directory that is guaranteed not to exist before you create it.

The function below, called extend_dir_name, extends base directory name ( passed as the first argument ) by a word ( passed as the second argument ) recursively. It returns a new directory name, which is a combination of first and second argument joined with forward slash, only if that combination is not an existent directory name on the system. Otherwise it keeps on requesting for a new word.


extend_dir_name(){

  ext="$2"
  base="$1"
  regex='/$'
  
  [[ ! -d "$base" ]] && {
    echo "$base must be an existing directory."
    exit 1
  }

  [[ ! $base =~ $regex ]] && {
    base="$base/"
  }
  
  [[ -z "$ext" ]] && {
    echo "Type name of directory extension (single word):"
    read -r ext
  }
  
  expanded="${base}${ext}"
  [[ ! -d "$expanded" ]] && {
    # return the new name     
    echo "$expanded"
    return
  # try again  
  } || {
    ext=
    extend_dir_name "$base" $ext
  }
}

Run it:


$ mkdir $( extend_dir_name /tmp/ tmp_dir_extension )

Checking for existing system user

Next up is a function that asks for an existing system user name to be provided. At first it checks global variable USER ( it should be declared in the script ) for the name. If it finds nothing assigned it uses 'read' to get a name. In the next step the function tries to verify the name using 'id' utility. It calls itself again if if finds there is no user with the name on the system.


check_user_exists(){
  recursive
  local user=${user:-${USER}}
  local exists=
  [[ -z "$user" ]] && {
    echo "Give existing user name:"
    read -r user
  }
  exists=$(id -u "$user" > /dev/null 2>&1; echo $?)
  [[ "$exists" == "0" ]] && {
    USER="$user"
    return
  }
  check_user_exists
}

Checking for existence of mysql database

Next function is somewhat similar to the previous one. When called with a name as its first argument it checks if a mysql database with the name exists on the system. When called with no arguments it ask for a name before carrying out the check. If it cannot find a database with the given name it will assign the name to DBNAME. Otherwise it will call itself.

When the function is called by the privileged user it looks up a name among all available database names, otherwise it sees only what is available to the current user.

For this function to work, mysql access (user and password) has to be configured in ~/.my.cnf (/root/.my.cnf for root user) file first.


check_mysqldb_exists() {
  local mysqldb=${mysqldb:-$1}
  local is_db=
  [[ -z "${mysqldb}" ]] && {
    echo "Specify database name:"
    read -r mysqldb
  }
 
  is_db=$(mysql -s -N -e "SELECT schema_name FROM information_schema.schemata WHERE schema_name = '${mysqldb}'" information_schema)
   
  [[ -z "$is_db" ]] && {
    DBNAME="${mysqldb}"
    echo "Setting DBNAME to ${mysqldb}"
    return
  } 
   
  echo "Database ${mysqldb} already exists."
   
  mysqldb=
  check_mysqldb_exists "$mysqldb"
}     

Escaping globs and quotes

Recursion is often leveraged to write filters. Here is one of these. escape_globs_qotes_rec accepts a string as its input. It scans it in search of glob characters (glob characters in BASH are *, ?, [, ] ) and quotes ( ' and " ) which then it escapes using backslash (backslash also gets escaped in the process).


escape_globs_qotes_rec(){
  local ESCD=${ESCD:-''}
  local -i count=${#1} 
  local str="$1"
 
  (( $count < 1 )) && { 
    echo "$ESCD"
    return
  }
   
  read -r -n 1 -d '' <<< "$str"
   
  case "$REPLY" in
    '\'| "'" | '"' | '*' | '?' | '[' | ']')
      ESCD+="\\${REPLY}"
    ;;
    *)
      ESCD+="$REPLY"
    ;;   
  esac
   
  escape_globs_qotes_rec "${str:1:$count}" 
}

Calling it might look like this:


escape_globs_qotes_rec "$(< file_with_data_to_escape )"

Unfortunately, possibly due to use of parameter expansions the function is rather slow. It is much slower than its equivalent that is based on while loop:


escape_globs_quotes(){
  ESCD=''
  while read -r -n 1 -d ''; do
    case "$REPLY" in
    '\'| "'" | '"' | '*' | '?' | '[' | ']')
      ESCD+='\'"$REPLY"
    ;;
    *)
      ESCD+="$REPLY"
    ;;
  esac 
  done <<< "$1"
  echo "$ESCD"
}

You would call escape_globs_quotes the same way as you did for escape_globs_quotes_rec :


escape_globs_quotes "$(< file_with_data_to_escape )"

Back to good old loops ...

This post was updated on 06 Oct 2021 21:10:21

Tags:  BASH ,  mysql ,  recursion 


Author, Copyright and citation

Author

Sylwester Wojnowski

Author of the this article - Sylwester Wojnowski - is a sWWW web developer. He has been writing computer code for the websites and web applications since 1998.

Copyrights

©Copyright, 2024 Sylwester Wojnowski. This article may not be reproduced or published as a whole or in parts without permission from the author. If you share it, please give author credit and do not remove embedded links.

Computer code, if present in the article, is excluded from the above and licensed under GPLv3.

Citation

Cite this article as:

Wojnowski, Sylwester. "BASH recursion examples - part 2." From sWWW - Code For The Web . https://swww.com.pl//main/index/bash-recursion-examples-part-2

Add Comment

Allowed BB Code - style tags: [b][/b], [i][/i], [code=text][/code],[code=javascript][/code],[code=php][/code],[code=bash][/code],[code=css][/code],[code=html][/code]


I constent to processing my data given through this form for purposes of a reply by the administrator of this website.

Recent Comments

Nobody has commented on this post yet. Be first!

Post navigation

Previous:
  Essential WordPress plugin hooks

Next:
  Arrays in BASH