Use variables with dot in unix shell script - bash

How do I use variables with dot in a Unix (Bash) shell script? I have the script below:
#!/bin/bash
set -x
"FILE=system.properties"
FILE=$1
echo $1
if [ -f "$FILE" ];
then
echo "File $FILE exists"
else
echo "File $FILE does not exist"
fi
This is basically what I need ⟶ x=propertyfile, and propertyfile=$1. Can someone please help me?

You can't declare variable names with dots but you can use associative arrays to map keys, which is the more appropriate solution. This requires Bash 4.0.
declare -A FILE ## Declare variable as an associative array.
FILE[system.properties]="somefile" ## Assign a value.
echo "${FILE[system.properties]}" ## Access the value.

Note that the line:
"FILE=system.properties"
tries to execute a command FILE=system.properties which most likely doesn't exist. To assign to a variable, the quote must come after the equals:
FILE="system.properties"
It is a bit hard to tell from the question what you are after, but it sounds as if you might be after indirect variable names. Unfortunately, standard editions of bash don't allow dots in variable names.
However, if you used an underscore instead, then:
FILE="system_properties"
system_properties="$1"
echo "${FILE}"
echo "${!FILE}"
will echo:
system_properties
what-was-passed-as-the-first-argument

Related

How do I pass in an un-evaluated variable to a shell script, and have the script evaluate it after sourcing another script?

I have a script that is sourcing a second script and need to do the following:
I want to pass in a variable name to the first script like this: sh firstScript.sh variable=$variableName
The first script will then source the second script, which contains the value of variableName
I'm then going to print the evaluated variable
I know that I can do something like \$variableName to pass in the variable name, but I can't figure out how to get the first script to then evaluate the variable using the exported variables from the second script. What am I missing here?
Here's what I wound up doing:
I'm passing in an entire string, with the variable embedded in the middle of the string like so:
sh firstScript.sh --message="This is a message \${variableName}"
In the first script, I'm doing these steps:
Extract the entire string in to an array
Pull out the embedded variables
Evaluate the embedded variables against the sourced script
Do a string replace to put the value in the original string
When I was done, it looked like this:
IFS=';' args=(${#//--/;}); unset IFS
source secondScript.sh
case ${arg^^} in
MESSAGE=*)
message="${arg#*=}"
messageVars=$(echo ${message} | grep -o "\${\w*}")
for messageVar in ${messageVars[*]}; do
messageVar=${messageVar#*\{}
messageVar=${messageVar%\}*}
messageVarVal=${!messageVar}
echo "messageVar: ${messageVar}"
echo "messageVarVal: ${messageVarVal}"
message=${message//"\${${messageVar}}"/"${messageVarVal}"}
done
echo "Message: ${message}"
;;
esac
I hope this is what you want:
firstScript.sh
varname="$1"
if [[ -z $varname ]]; then
echo "usage: $0 varname"
exit
fi
source ./secondScript.sh
declare -n ref="$varname"
echo "varname=$varname value=$ref"
secondScript.sh
foo=2
Then execute the script with:
./firstScript.sh foo
Output:
varname=foo value=2
The -n option to the declare creates a reference to another variable.
The bash version should be 4.3 or later to enjoy the functionality.
[Alternative]
You can also write the firstScript.sh as:
firstScript.sh
varname="$1"
if [[ -z $varname ]]; then
echo "usage: $0 varname"
exit
fi
source ./secondScript.sh
echo "varname=$varname value=${!varname}"
which will produce the same result.
The ${!varname} notation is called indirect expansion in which varname is expanded as a name of a variable.
It has been introduced since bash2.
Hope this helps.

How to pass shell variables in "echo" command

I have she script as the below content
chr=$0
start=$1
end=$2
echo -e "$chr\t$start\t$end" > covdb_input.bed
How do i pass the chr,Start and end variables in to echo command.. or write same to file "covdb_input.bed" with TAB sep as in echo command.
You're doing everything right, except that you probably initialize your variables with the wrong things.
I'm assuming you get arguments for the script (or shell function), and that you want to use these. Then pick the positional variables from $1 and onwards as $0 will usually contain the name of the current shell script or shell function.
Also, you might find people scoffing about the use of -e with echo (it's a common but non-standard option). Instead of using echo you could use printf like this:
printf "%s\t%s\t%s" "$chr" "$start" "$end" >myfile.bed
Or just
printf "$chr\t$start\t$end" >myfile.bed

How do I tell if a variable is a file in bash

I am not a great bash scripter and hence have a few questions. One of which is how (or even whether) bash understands that a variable is a "file" or simply a local variable.
file=/usr/share/lib
Obviously this is a file to be saved, etc and can be used like so:
echo "$output" > $file
To save the output of $output to $file.
But where in bash does it calculate whether it's a file or not, is it only a file once it's been passed to a 'writing method'?
If you treat that variable as a file name, bash will simply do what it's told e.g.
echo "Test output" > $file
will work regardless of file being set to /tmp/myfile.txt, or to abcd. In the above you're using bash's file redirection to write out the standard out to the file you've named.
Consequently if you use the wrong variable in the above pattern, or have the value set incorrectly, bash will simply follow your instructions and you may end up with incorrectly named/located files.
You need to check this yourself, bash is only aware of the contents of the variable. If you want to check if a file location is held within a variable, you can test for it using the -f test operator
if [ -f "$file" ]
then
echo "$file is a file"
echo "$output" > "$file"
else
echo "$file is not a file"
fi
Variables are strings - nothing more, nothing less. (I'm ignoring array variables here, WLOG).
Bash (or any shell) expands variables blindly, without considering what you intend to do with them. It's only in the next stage of command processing that the contents of the string matter.
To use your example:
output="foo bar"
file=/usr/share/lib
echo "$output" >"$file"
(I've quoted $file even though it's not necessary here, simply because I've been bitten too many times by changing the value and breaking everything).
The line
echo "$output" >"$file"
gets transformed into
echo "foo bar" >"/usr/share/lib"
and only then does bash consider the > and attempt to open /usr/share/lib for writing.

Building an execution string from a text file

I am creating a shell script that retrieves values from the database and spools into a text file. Those values will have variables ($CURR_DATE $SITE etc...) in the database. So when I want to execute the program with those variables I run into an issue where it is using the literal string and not the value from the variable.
for example.
while read line;
do
Unix_Array[$counter]=$line;
let counter=counter+1;
done < parameterfile.txt
echo "Finished putting into array"
while [[ $c -lt ${#Unix_Array[#]} ]]
do
PARAMS="${PARAMS:-}${PARAMS:+ }${Unix_Array[$c]}"
((c=$c+1))
done
echo "Finished creating parameter string"
EXECUTE="$PROGRAM $USERID $PARAMS"
echo $PARAMS
$EXECUTE
I think it is executing like
Program user/id#DB $CURR_DATE $SITE
instead of the actual variables that were declared and already set.
How can i build the execution statement so that it will use the variables declared and not the literal variable.
Once you've collected the array, use it directly:
typeset -a params
while IFS= read -r line; do
params[n++]=$line;
done < parameterfile.txt
"$program" "$userid" "${params[#]}"
As to the lines containing variables, I'd hesitantly recommend using eval. What does the parameter file look like? Who has permission to write to it?
Get out of the habit of using ALL_CAPS_VARS: one day you'll use PATH or LANG and wonder why things "don't work".

Specify command line arguments like name=value pairs for shell script

Is it possible to pass command line arguments to shell script as name value pairs, something like
myscript action=build module=core
and then in my script, get the variable like
$action and process it?
I know that $1....and so on can be used to get variables, but then won't be name value like pairs. Even if they are, then the developer using the script will have to take care of declaring variables in the same order. I do not want that.
This worked for me:
for ARGUMENT in "$#"
do
KEY=$(echo $ARGUMENT | cut -f1 -d=)
KEY_LENGTH=${#KEY}
VALUE="${ARGUMENT:$KEY_LENGTH+1}"
export "$KEY"="$VALUE"
done
# from this line, you could use your variables as you need
cd $FOLDER
mkdir $REPOSITORY_NAME
Usage
bash my_scripts.sh FOLDER="/tmp/foo" REPOSITORY_NAME="stackexchange"
STEPS and REPOSITORY_NAME are ready to use in the script.
It does not matter what order the arguments are in.
Changelog
v1.0.0
In the Bourne shell, there is a seldom-used option '-k' which automatically places any values specified as name=value on the command line into the environment. Of course, the Bourne/Korn/POSIX shell family (including bash) also do that for name=value items before the command name:
name1=value1 name2=value2 command name3=value3 -x name4=value4 abc
Under normal POSIX-shell behaviour, the command is invoked with name1 and name2 in the environment, and with four arguments. Under the Bourne (and Korn and bash, but not POSIX) shell -k option, it is invoked with name1, name2, name3, and name4 in the environment and just two arguments. The bash manual page (as in man bash) doesn't mention the equivalent of -k but it works like the Bourne and Korn shells do.
I don't think I've ever used it (the -k option) seriously.
There is no way to tell from within the script (command) that the environment variables were specified solely for this command; they are simply environment variables in the environment of that script.
This is the closest approach I know of to what you are asking for. I do not think anything equivalent exists for the C shell family. I don't know of any other argument parser that sets variables from name=value pairs on the command line.
With some fairly major caveats (it is relatively easy to do for simple values, but hard to deal with values containing shell meta-characters), you can do:
case $1 in
(*=*) eval $1;;
esac
This is not the C shell family. The eval effectively does the shell assignment.
arg=name1=value1
echo $name1
eval $arg
echo $name1
env action=build module=core myscript
You said you're using tcsh. For Bourne-based shells, you can drop the "env", though it's harmless to leave it there. Note that this applies to the shell from which you run the command, not to the shell used to implement myscript.
If you specifically want the name=value pairs to follow the command name, you'll need to do some work inside myscript.
It's quite an old question, but still valid
I have not found the cookie cut solution. I combined the above answers. For my needs I created this solution; this works even with white space in the argument's value.
Save this as argparse.sh
#!/bin/bash
: ${1?
'Usage:
$0 --<key1>="<val1a> <val1b>" [ --<key2>="<val2a> <val2b>" | --<key3>="<val3>" ]'
}
declare -A args
while [[ "$#" > "0" ]]; do
case "$1" in
(*=*)
_key="${1%%=*}" && _key="${_key/--/}" && _val="${1#*=}"
args[${_key}]="${_val}"
(>&2 echo -e "key:val => ${_key}:${_val}")
;;
esac
shift
done
(>&2 echo -e "Total args: ${#args[#]}; Options: ${args[#]}")
## This additional can check for specific key
[[ -n "${args['path']+1}" ]] && (>&2 echo -e "key: 'path' exists") || (>&2 echo -e "key: 'path' does NOT exists");
#Example: Note, arguments to the script can have optional prefix --
./argparse.sh --x="blah"
./argparse.sh --x="blah" --yy="qwert bye"
./argparse.sh x="blah" yy="qwert bye"
Some interesting use cases for this script:
./argparse.sh --path="$(ls -1)"
./argparse.sh --path="$(ls -d -1 "$PWD"/**)"
Above script created as gist, Refer: argparse.sh
Extending on Jonathan's answer, this worked nicely for me:
#!/bin/bash
if [ "$#" -eq "0" ]; then
echo "Error! Usage: Remind me how this works again ..."
exit 1
fi
while [[ "$#" > "0" ]]
do
case $1 in
(*=*) eval $1;;
esac
shift
done

Resources