obtain dynamically executable path depending on script variable avoiding many ifs - bash

I want to run determined executable depending on a variable of my script cur_num
I have this code, where the paths are "random", meaning they have nothing to do with the secuential cur_num:
run() {
if [ $cur_num = "0" ]
then ~/pathDirName0/execFile0
elif [ $cur_num = "1" ]
then ~/pathDirName1/execFile1
elif [ $cur_num = "2" ]
then ~/pathDirName2/execFile2
elif [ $cur_num = "3" ]
then ~/pathDirName3/execFile3
elif [ $cur_num = "4" ]
then ~/pathDirName4/execFile4
fi
}
In case there are lots more of cases, that resulys in a very long if - elif statement.
As there are no enums in bash to build the cur_num- path relation, is there a cleaner way to obtain the desired path dynamically instead of with lots of ifs?

Try case
case $cur_num in
0) ~/pathDirName0/execFile0;;
1) ~/pathDirName1/execFile1;;
2) ~/pathDirName2/execFile2;;
...
esac

set allows you to create arrays in a portable manner, so you can use that to create an ordered array:
set -- ~/pathDirName0/execFile0 ~/pathDirName1/execFile1 ~/pathDirName2/execFile2 ~/pathDirName3/execFile3 ~/pathDirName4/execFile4
Then to access these items via an index you do $x where x is the index of the item.
Now your code would look something like this:
run() {
original="$#"
: $((cur_num+=1)) # Positional param indexing starts from 1
set -- ~/pathDirName0/execFile0 ~/pathDirName1/execFile1 \
~/pathDirName2/execFile2 ~/pathDirName3/execFile3 \
~/pathDirName4/execFile4
eval "$"$cur_num"" # cur_num = 1, accesses $1 == execFile0
set -- $original # Restore original args
}
A method that is both less and more hacky that would work for indexes above 9 and not mess with the original positional parameters nor use eval
run() {
count=0
for item in "$#"; do
[ "$count" = "$cur_num" ] && {
"$item"
return
}
: "$((count+=1))"
done
echo "No item found at index '$cur_num'"
}
cur_num="$1" # Assuming first arg of script.
run ~/pathDirName0/execFile0 ~/pathDirName1/execFile1 \
~/pathDirName2/execFile2 ~/pathDirName3/execFile3 \
~/pathDirName4/execFile4

Related

Shell Function to get value of a property from YAML file

I am trying to create a unix shell/bash function to get a specific value from a YAML file.
I don't want to use Python/Ruby or other scripting languages or do not want to install any packages that are not natively available in bash.
The function should take the property name name and yaml file name as input and return the value of the property.
Sample YAML is given below.
---
Action: start
Version: 642
Domains:
Domain:
Name: SanityTest
AppSpaces:
AppSpace:
Name: SanityAppSpace
AppNodes:
AppNode:
Name: SanityAppnode
Applications:
Application:
Name: InstagramTest.application
Version: "1.0"
AppNode:
Name: SanityAppnode_1
Applications:
Application:
Name: InstagramTest.application
Version: "1.0"
I am looking for some function that works like:
DomainName=getValue Domains_Domain[1]_Name /path/my_yaml.yml
AppSpaceName=getValue Domains_Domain[$DomainName]_AppSpaces_AppSpace[1]_Name /path/my_yaml.yml
AppNodeName=getValue Domains_Domain[$DomainName]_AppSpaces_AppSpace[$AppSpaceName]_AppNodes_AppNode[1]_Name /path/my_yaml.yml
AppName=getValue Domains_Domain[$DomainName]_AppSpaces_AppSpace[$AppSpaceName]_AppNodes_AppNode[$AppNodeName]_Applications_Application[1]_Name /path/my_yaml.yml
AppVersion=getValue Domains_Domain[$DomainName]_AppSpaces_AppSpace[$AppSpaceName]_AppNodes_AppNode[$AppNodeName]_Applications_Application[$AppName]_Version /path/my_yaml.yml
I came up with the below code so far, but it is not working as expected. Appreciate any help. Please advise if my approach is wrong and if there is better way.
function getValue {
local ymlFile=$2
local Property=$1
IFS='_' read -r -a props_tokens <<< "${Property}"
local props_index=0
echo "${props_tokens[props_index]}"
CheckingFor="Domains"
currEntity_Index=0
while IFS= read -r line; do
curr_prop="${props_tokens[props_index]}"
curr_prop_Index=0
IFS=':' read -r -a curr_line_props <<< "${line}"
curr_line_prop = ${curr_line_props[0]}
if [ "${props_tokens[props_index]}" == *"["*"]"* ]
then
IFS='[' read -r -a prop_tokens <<< "${props_tokens[props_index]}"
curr_prop=${prop_tokens[0]}
curr_prop_Index=${prop_tokens[1]::-1}
echo "Index processed"
echo $curr_prop
echo $curr_prop_Index
else echo "No Index for this property"
fi
if [ "$curr_line_prop" != "$CheckingFor" ]
then continue
fi
if [ "|Action|Name|Version|" == *"$curr_prop"* ]
then
return curr_line_props[1]
fi
if [ "$curr_prop" == "$CheckingFor" ]
then
if [ "|Domains|AppSpaces|AppNodes|Applications|" == *"$curr_prop"* ]
then
props_index++
case $CheckingFor in
Domains)
CheckingFor="Domain";;
Domain)
CheckingFor="AppSpaces";;
AppSpaces)
CheckingFor="AppSpace";;
AppSpace)
CheckingFor="AppNodes";;
AppNodes)
CheckingFor="AppNode";;
AppNode)
CheckingFor="Applications";;
Applications)
CheckingFor="Application";;
*) echo "$curr_prop - not switched";;
esac
continue
else
if [ "$curr_prop_Index" == 0 ]
then
case $CheckingFor in
Domains)
CheckingFor="Domain";;
Domain)
CheckingFor="AppSpaces";;
AppSpaces)
CheckingFor="AppSpace";;
AppSpace)
CheckingFor="AppNodes";;
AppNodes)
CheckingFor="AppNode";;
AppNode)
CheckingFor="Applications";;
Applications)
CheckingFor="Application";;
*) echo "$curr_prop - not switched";;
esac
props_index++
continue
else
Integer_regex='^[0-9]+$'
if [[ $curr_prop_Index =~ $Integer_regex ]]
then
# Iterate until we get to the nth Item
currEntity_Index++
if [ $currEntity_Index == $curr_prop_Index ]
then
case $CheckingFor in
Domains)
CheckingFor="Domain";;
Domain)
CheckingFor="AppSpaces";;
AppSpaces)
CheckingFor="AppSpace";;
AppSpace)
CheckingFor="AppNodes";;
AppNodes)
CheckingFor="AppNode";;
AppNode)
CheckingFor="Applications";;
Applications)
CheckingFor="Application";;
*) echo "$curr_prop - not switched";;
esac
currEntity_Index=0
else
fi
props_index++
continue
else
# Iterate until the name of the entity match and continue with next property token
# How to handle if the search sipllied into next object?
fi
fi
fi
else
props_index++
continue
fi
done < $ymlFile
return "${props_tokens[props_index]}"
}

Perl one liner in Bash script

I have a bash script that runs, and I'm trying to use a Perl one-liner to replace some text in a file variables.php
However, I would like to check if the Perl one-liner runs successfully and that's where I get hung up. I could just output the one-liner and it would work fine, but I would like to know for sure that it ran.
Basically, the function replace_variables() is the function that does the update, and it's the if statement there that I would like to check if my one-liner worked properly.
I've tried using the run_command function in that if statement, but that did not work, and I've tried putting the one-liner directly there, which also didn't work.
If I don't wrap it in an if statement, and just call the one-liner directly, everything works as intended.
here's the full file
#!/bin/bash
export CLI_CWD="$PWD"
site_variables() {
if [ -f "$CLI_CWD/variables.php" ]; then
return true
else
return false
fi
}
replace_variables() {
# perl -pi -e 's/(dbuser)(\s+)=\s.*;$/\1 = Config::get("db")["user"];/; s/(dbpass)(\s+)=\s.*;$/\1 = Config::get("db")["pass"];/; s/(dbname)(\s+)=\s.*;$/\1 = Config::get("db")["database"];/' "$CLI_CWD/variables.php"
if [run_command ]; then
echo "Updated variables.php successfully"
else
echo "Did not update variables.php"
fi
}
run_command() {
perl -pi -e 's/(dbuser)(\s+)=\s.*;$/\1 = Config::get("db")["user"];/; s/(dbpass)(\s+)=\s.*;$/\1 = Config::get("db")["pass"];/; s/(dbname)(\s+)=\s.*;$/\1 = Config::get("db")["database"];/' "$CLI_CWD/variables.php"
}
if [ site_variables ]; then
replace_variables
else
>&2 echo "Current directory ($(pwd)) is not a project root directory"
exit 4
fi
here's the function where the if statement fails
replace_variables() {
# perl -pi -e 's/(dbuser)(\s+)=\s.*;$/\1 = Config::get("db")["user"];/; s/(dbpass)(\s+)=\s.*;$/\1 = Config::get("db")["pass"];/; s/(dbname)(\s+)=\s.*;$/\1 = Config::get("db")["database"];/' "$CLI_CWD/variables.php"
if [run_command ]; then
echo "Updated variables.php successfully"
else
echo "Did not update variables.php"
fi
}
You can see that I commented out the one-liner just before the if statement, it works if I let that run and remove the if/else check.
here is the original file snippet before the update
//Load from Settings DB
$dbuser = 'username';
$dbpass = 'password';
$dbname = 'database_name';
here is the file snippet after the update would run
//Load from Settings DB
$dbuser = Config::get("db")["user"];
$dbpass = Config::get("db")["pass"];
$dbname = Config::get("db")["database"];
tl;dr and Solution
This usage of if with [ ] will not give you the result you expect.
What you're looking for
...
if run_command; then
...
Longer explanation
Basics of if
if is a shell feature
based on the condition, it executes the body contained in between then and fi
the "condition" that if checks is a command
commands usually have a return/exit code. typically
0 for success
1 (common) and everything else for some error
e.g. 127 for command not found
when the return/exit code is 0, the body is executed
otherwise it is skipped; or control is passed to elif or else
the syntax is if <command>; then...
Where does that [ ] come from?
test is a command that can check file types and compare values
refer man test and help test (bash only)
[ ... ] is a synonym for test
NB the brackets should be surrounded by spaces on both sides
if [ -f "/path/to/$filename" ]; then
exception: when terminated by new line or ; space not required
test (or [ ]) evaluates expressions and cannot execute other commands or functions
if [ expr ]; then is alternate syntax for if test expr; then
PS: good practice to "quote" your "$variables" when used with test or [ ]
PPS: [[ ... ]] is a different thing altogether. not POSIX; available only in some shells. take a look at this thread on the UNIX Stack Exchange

Bash script to pass variable across functions

I have removed parentheses, but still I was not able to fetch ENV_NODE value in second function scpTAR. Please let me know what is wrong.
set -x
MASTER_HOSTNAME=`hostname | cut -d . -f1`
TARGET_ENVIRONMENT = it
evaluateEnvProp(){
if [ ${TARGET_ENVIRONMENT} = it ]; then
ENV_NAME=it && ENV_NODE=1cf62108e084
fi
}
scpTAR() {
echo ENV_NODE
echo ${ENV_NODE}
if [ ENV_NODE = ${MASTER_HOSTNAME} ] ; then
echo "scpTAR ENV_NODE = ${MASTER_HOSTNAME} "
else
"echo 'scpTAR ssh other node than jenkins server ENV_NODE=${MASTER_HOSTNAME}'"
fi
}
main(){
scpTAR
}
main
As #cyrus said, variables are global by default. What you did is setting the variables in a subshell:
( ENV_NAME=it && ENV_NODE=xyx && ENV_WLS_DOMAIN=user1 && ENV_NODE_PATH=path )
Because of that, these are gone (not propagated to calling script's environment) once the subshell exists. This is why you do not see their values set in scpTAR function. Remove the parentheses and your code should start working.
Update
Updated version of your code (based on answer by itChi) has another major error. You put spaces around the assignment operator when setting TARGET_ENVIRONMENT = it. This syntax is invalid and as a result TARGET_ENVIRONMENT is not assigned the specified value, thus the condition inside evaluateEnvProp function evaluates to false and ENV_NODE variable is not being set. Removing the spaces should solve the problem. You also did not call evaluateEnvProp as pointed out in update to #itChi's answer.
I'd highly recommend that you start using ShellCheck to verify correctness of your scripts.
As mentioned, variables are global by default, so if you reference them in your scpTAR function, you will get a return value.
However, as a second method, should you wish, you can reference it like so:
scpTAR $ENV_NAME $ENV_NODE $ENV_WLS_DOMAIN $ENV_NODE_PATH
Then in your scpTAR function reference them as:
echo "$1 $2 $3 $4"
it xyx user1 path
Particularly useful should you wish to run code on another machine, run a remote script, or set your script up to pass variables as arguments from bash.
EDIT:
Sorry if I tread on someone's toes, but here is your answer without subshell:
evalEnvProp(){
if [ ${TARGET_ENVIRONMENT} = "it" ]; then
ENV_NAME=it
ENV_NODE=xyx
ENV_WLS_DOMAIN=user1
ENV_NODE_PATH=path
fi
}
scpTAR() {
echo $ENV_NODE_PATH
}
main(){
evalEnvProp
scpTAR
}
main
Update2:
#!/bin/bash
set -x
MASTER_HOSTNAME=`hostname | cut -d . -f1`
TARGET_ENVIRONMENT=it
evaluateEnvProp(){
if [ ${TARGET_ENVIRONMENT} = "it" ]; then
ENV_NAME=it && ENV_NODE=1cf62108e084
fi
}
scpTAR() {
echo ENV_NODE
echo ${ENV_NODE}
if [ ENV_NODE == ${MASTER_HOSTNAME} ] ; then
echo "scpTAR ENV_NODE = ${MASTER_HOSTNAME} " else
"echo 'scpTAR ssh other node than jenkins server >ENV_NODE=${MASTER_HOSTNAME}'"
fi
}
main(){
evaluateEnvProp
scpTAR
}
main
MASTER_HOSTNAME needs `` for the command. you are calling hostname. You can also accomplish this with $()
Spacing in the IF statement either side of the = sign. otherwise you are not evaluating, you are setting a variable.
In your edit you missed out a function call to evaluateEnvProp which failed because you didn't set the variable.

Binary tree of directories UNIX

I have a task to create a binary tree of directories in bash shell, the depth is given as a first argument of the script. Every directory has to be named with the second argument + the depth of the tree which the directory is in.
Example: ./tree.sh 3 name should create the following structure:
name11
/ \
name21 name22
/ \ / \
name31 name32 name33 name34
I don't really have an idea how to do this, Can't even start. It is harder than anything i have done in bash up until now.. Any help will be very much appreciated.
Thanks in advance.
With recursion:
#!/bin/bash
level=$1
current_level=$2; current_level=${current_level:=1}
last_number=$3; last_number=${last_number:=1}
prefix="name"
# test to stop recursion
[[ $level -eq $(($current_level-1)) ]] && exit
# first node
new_number=$(($current_level*10+$last_number*2-1))
mkdir "$prefix$new_number"
(
cd "$prefix$new_number"
$0 $level $(($current_level+1)) $(($last_number*2-1)) &
)
# second node, not in level 1
if [[ $current_level -ne 1 ]]; then
new_number=$(($current_level*10+$last_number*2))
mkdir "$prefix$new_number"
cd "$prefix$new_number"
$0 $level $(($current_level+1)) $(($last_number*2)) &
fi
Test with ./tree.sh 3
Even though other languages are more suitable in implementing a link list, I don't know why this post got a negative vote.
Here's this expert, shared something good for searching, take a look:
https://gist.github.com/iestynpryce/4153007
NOTE: An implementation of a Binary Sort Tree in Bash. Object-like behaviour has been faked using eval. Remember that eval in shell scripting can be evil. BT and BST have difference, you can google it.
#!/bin/bash
#
# Binary search tree is of the form:
# 10
# / \
# / \
# 4 16
# / \ /
# 1 7 12
#
# Print the binary search tree by doing a recursive call on each node.
# Call the left node, print the value of the current node, call the right node.
# Cost is O(N), where N is the number of elements in the tree, as we have to
# visit each node once.
print_binary_search_tree() {
local node="$*";
# Test is the node id is blank, if so return
if [ "${node}xxx" == "xxx" ]; then
return;
fi
print_binary_search_tree $(eval ${node}.getLeftChild)
echo $(${node}.getValue)
print_binary_search_tree $(eval ${node}.getRightChild)
}
### Utility functions to generate a BST ###
# Define set 'methods'
set_node_left() {
eval "${1}.getLeftChild() { echo "$2"; }"
}
set_node_right() {
eval "${1}.getRightChild() { echo "$2"; }"
}
set_node_value() {
eval "${1}.getValue() { echo "$2"; }"
}
# Generate unique id:
gen_uid() {
# prefix 'id' to the uid generated to guarentee
# it starts with chars, and hence will work as a
# bash variable
echo "id$(uuidgen|tr -d '-')";
}
# Generates a new node 'object'
new_node() {
local node_id="$1";
local value="$2";
local left="$3";
local right="$4";
eval "${node_id}set='set'";
eval "set_node_value $node_id $value";
eval "set_node_left $node_id $right";
eval "set_node_right $node_id $right";
}
# Inserts a value into a tree with a root node with identifier '$id'.
# If the node, hence the tree does not exist it creates it.
# If the root node is at the either end of the list you'll reach the
# worst case complexity of O(N), where N is the number of elements in
# the tree. (Average case will be 0(logN).)
tree_insert() {
local id="$1"
local value="$2";
# If id does not exist, create it
if [ -z "$(eval "echo \$${id}set")" ]; then
eval "new_node $id $value";
# If id exists and the value inserted is less than or equal to
# the id's node's value.
# - Go down the left branch
elif [[ $value -le $(${id}.getValue) ]]; then
# Go down to an existing left node if it exists, otherwise
# create it.
if [ "$(eval ${id}.getLeftChild)xxx" != "xxx" ]; then
tree_insert $(eval ${id}.getLeftChild) $value
else
local uid=$(gen_uid);
tree_insert $uid $value;
set_node_left $id $uid;
fi
# Else go down the right branch as the value inserted is larger
# than the id node's value.
else
# Go down the right node if it exists, else create it
if [ "$(eval ${id}.getRightChild)xxx" != "xxx" ]; then
tree_insert $(eval ${id}.getRightChild) $value
else
local uid=$(gen_uid);
tree_insert $uid $value;
set_node_right $id $uid;
fi
fi
}
# Insert an unsorted list of numbers into a binary search tree
for i in 10 4 16 1 7 12; do
tree_insert bst $i;
done
# Print the binary search tree out in order
print_binary_search_tree bst
Actually, I think, it's super easy to implement aa BST in BASH.
How:
Just create a :) damn .txt :) FILE for maintaining the BST.
Here, I'm not going to show how you can implement the CRUD operation for inserting/populating or deleting/updating a BST nodes if implemented using a simple .txt file, but it works as far as printing values. I'll work on it and share the solution soon.
Here is my solution: Just FYSA In BASH, I used a .txt file approach and tried for printing the same from any root node here: https://stackoverflow.com/a/67341334/1499296

bash 4.x assoc array loses keys in receiving function

I am trying to pass an associative array from one function to another, and am losing named index keys (e.g., filepath, search in example below) though the array passed in can access its elements correctly using indexes 0, 1. I must be doing something slightly wrong with bash syntax, but can't quite figure out where. Any help appreciated.
Using GNU bash, version 4.3.8 from Ubuntu 14.04
Just below is bash code example, and at bottom is output
#! /bin/bash
function test_function {
func_data=("${#}")
# without brackets above cannot access func_data[1]
# local ${!func_data}
# the above local statement does not seem to help either way
echo ""
for K in "${!func_data[#]}"; do echo $K; done
echo ""
echo "func_data : ${func_data}"
echo "func_data[filepath] : ${func_data[filepath]}"
echo "func_data[search] : ${func_data[search]}"
# all three echos above output first element of array,
# which is 'style "default" {' during first loop
# Can access array elements 0, 1 but no longer via filepath, search
echo "func_data[0] : ${func_data[0]}"
echo "func_data[1] : ${func_data[1]}"
echo "!func_data[#] : ${!func_data[#]}"
# echo above outputs '0 1' so indexes now are now zero based?
echo "func_data[#] : ${func_data[#]}"
# echo above outputs all array elements 'style "default" { ~/.gtkrc-2.0'
}
# In BASH, local variable scope is the current function and every
# child function called from it, so provide a function main to
# make it possible to utilize variable scope to fix issues
function main {
echo ""
declare -A gtkrc2=()
gtkrc2[filepath]="~/.gtkrc-2.0"
gtkrc2[search]="style \"default\" {"
echo "gtkrc2 filepath : ${gtkrc2[filepath]}"
echo "gtkrc2 search : ${gtkrc2[search]}"
test_function "${gtkrc2[#]}"
echo ""
declare -A gtkcss=()
gtkcss[filepath]="~/.config/gtk-3.0/gtk.css"
gtkcss[search]=".scrollbar {"
echo "gtkcss filepath : ${gtkcss[filepath]}"
echo "gtkcss search : ${gtkcss[search]}"
test_function "${gtkcss[#]}"
}
main
---------- OUTPUT ----------
gtkrc2 filepath : ~/.gtkrc-2.0
gtkrc2 search : style "default" {
func_data : style "default" {
func_data[filepath] : style "default" {
func_data[search] : style "default" {
func_data[0] : style "default" {
func_data[1] : ~/.gtkrc-2.0
!func_data[#] : 0 1
func_data[#] : style "default" { ~/.gtkrc-2.0
gtkcss filepath : ~/.config/gtk-3.0/gtk.css
gtkcss search : .scrollbar {
func_data : .scrollbar {
func_data[filepath] : .scrollbar {
func_data[search] : .scrollbar {
func_data[0] : .scrollbar {
func_data[1] : ~/.config/gtk-3.0/gtk.css
!func_data[#] : 0 1
func_data[#] : .scrollbar { ~/.config/gtk-3.0/gtk.css
This may or may not be the "correct" way to do this but this is the best I can figure out. Any suggestions from others are welcome:
function test_function {
arrname=$1
idxlist="$2"
echo ""
echo "Array passed=$arrname"
for idx in $idxlist; do
elemname=$arrname[$idx]
echo "idx=$idx, elem=${!elemname}"
done
}
# In BASH, local variable scope is the current function and every
# child function called from it, so provide a function main to
# make it possible to utilize variable scope to fix issues
function main {
echo ""
declare -A gtkrc2=()
gtkrc2[filepath]="~/.gtkrc-2.0"
gtkrc2[search]="style \"default\" {"
echo "gtkrc2 filepath : ${gtkrc2[filepath]}"
echo "gtkrc2 search : ${gtkrc2[search]}"
test_function gtkrc2 "${!gtkrc2[*]}"
echo ""
declare -A gtkcss=()
gtkcss[filepath]="~/.config/gtk-3.0/gtk.css"
gtkcss[search]=".scrollbar {"
echo "gtkcss filepath : ${gtkcss[filepath]}"
echo "gtkcss search : ${gtkcss[search]}"
test_function gtkcss "${!gtkcss[*]}"
}
main
In particular:
To pass each associative array to the function, we pass both the name of the array, and its list of indices
Inside the function, the array name and index list are taken from the positional parameters
We may then loop over the list of indices and obtain the corresponding value of each element. This is done by first generating the name of the element, and then using the ! indirection modifier to get the actual value.
This technique of indirection of arrays is described in this question, but only addresses indexed arrays, and not associative arrays; passing the index list is one way I can think of to get this to work for associative arrays.

Resources