Parse ${} placeholder into absolute path in shell script - bash

I have a app.properties file something like below
Base.dir="/user/test/application"
Result.dir="${base.dir}/result"
and i've create bash script to parse above properties
function readConfigFile()
{
(grep -E "^${2}=" -m 1 "${1}" 2>/dev/null || echo "VAR=__UNDEFINED__") | head -n 1 | cut -d '=' -f 2-;
}
function setConfigFile()
{
sourceFile=${1}
}
function configGet()
{
if [ ! -z $sourceFile ]; then
val="$(readConfigFile $sourceFile "${1}")";
if [ "${val}" = "__UNDEFINED__" ]; then
echo "${1} value not exist"
# return empty string
printf -- "%s" "";
fi
printf -- "%s" "${val}";
else
echo "config file not exist"
# return empty string
printf -- "%s" "";
fi
}
and the way i call above parser is something like below
$Result_dir=$(configGet Result.dir)
however, i cant really translate placeholder ${} into base_dir
and i got following error
ls $Result_dir
ls: cannot access ${Base_dir}/result: No such file or directory
Is there any way that i can translate ${Base.dir} into /user/test/application?

I guess you're not going to be able to substitute ${base.dir} (btw shouldn't it be ${Base.dir}?) the way you were hoping mainly because, as far as I know, dots are not allowed in variable names in bash.
What you could do is manually substitute the ${base.dir} part with the corresponding path using bash's substitution syntax. For example:
setConfigFile 'app.properties'
Result_dir_raw=$(configGet Result.dir)
Result_dir=${Result_dir_raw/'${base.dir}'/$(configGet Base.dir)}
echo ${Result_dir}
I say "manually" because you still specify in your source code that the pattern you want to replace is ${base.dir} which I'm guessing isn't what you wanted.
Now if you run this you'll see that the ${Result_dir} variable evaluates to ""/user/test/application"/result" which obviously isn't a path, and this is because you're surrounding the paths in app.properties with double quotes, so you either need to get rid of them in your readConfigFile function or lose them altogether in your config file, which to me makes more sense.

Why have you a . in your variable name, which is not allowed in bash:
$ Base.dir="/user/test/application"
-bash: Base.dir=/user/test/application: No such file or directory
$ Base_dir="/user/test/application"
$
So, why do you get No such file or directory? Here is an explanation:
Create a file called Base.dir=gash.sh, yes, that's a legal filename
$ echo 'echo Hello World' > Base.dir=gash.sh
Make the file executable:
$ PATH=$PATH:.
$ chmod u+x Base.dir=gash.sh
Now type the command:
$ Base.dir="gash.sh"
Hello World
Use an underscore, not a dot. By the Way, ksh Korn shell not only allows the dot, it has a special meaning, it is a compound variable.

Related

How to compare a string variable against a literal in zsh

I'm trying to do an OS check in my .zshrc. I can't get the string comparison against Ubuntu to match correctly when running on Ubuntu.
Snippet:
function get_linux_distro()
{
echo `awk -F= '/^NAME/{print $2}' /etc/os-release`
}
function is_os_ubuntu()
{
set -x
local dist=`get_linux_distro`
if [[ ${dist} = "Ubuntu"* ]]; then # <<< string comp
echo "UBUNTU"
return 0
else
echo "BLAHHHH"
return 1
fi
}
Output:
❯ is_os_ubuntu
+is_os_ubuntu:4> get_linux_distro
+get_linux_distro:3> awk '-F=' '/^NAME/{print $2}' /etc/os-release
+get_linux_distro:3> echo '"Ubuntu"'
+is_os_ubuntu:4> local dist='"Ubuntu"'
+is_os_ubuntu:5> [[ '"Ubuntu"' = Ubuntu* ]] # <<< don't match due to quotes??
+is_os_ubuntu:10> echo BLAHHHH
BLAHHHH
+is_os_ubuntu:11> return 1
Note: I've added the bash tag since I'm lead to believe this is the same in both and bash has more visibility.
How to correctly compare a string variable against a litteral in zsh
Remove the * or quote it, otherwise, it's parsed as a glob expression. You are correctly comparing it.
[[ ${dist} = "Ubuntu" ]]
Because your string is not Ubuntu, but "Ubuntu", it's not equal and works correctly.
Please do not ask XY questions.
From man os-release:
The basic file format of os-release is a newline-separated list of environment-like shell-compatible variable assignments. [...]
Source the file in shell and output the variable, preferably in a subshell.
get_linux_distro() {
sh -c 'source /etc/os-release; echo "$NAME"'
}
Do not use backticks `. Prefer $(...).
Do not use: echo $(something) - it's a useless use of echo, like echo $(echo $(echo $(something))). Just do the thing you want to do, without echo.
Check your scripts with HTTP://shellcheck.net.
While the comments you got, and the answer given by KamilCuk. are of course correct, one alternative worth considering would be to use [[ $dist:l == *ubuntu* ]] for the test. This would test whether dist contains ubuntu as a substring, case insensitively.

Bash run command according to environment variable

See I wanna copy a file to a destination: cp filename /home/example/temp.txt.
The question the filename will be changed by some programes, and the new name of it will be written in file /home/example/.env.
What I want is alias something like alias cpf=cp ${filename} /home/nope/temp.txt to .bashrc, then what I need is only run cpf if I want to copy the latest finename to /home/example/temp.txt.
What I have tried:
eval $(grep -v "^#" "/home/example/.env") cp ${filename} /home/nope/temp.txt
and faild to get ${filename}.
Is there some changes to make what I tried work?
Example .env:
key1='do not put me in the environment'
key2=1231
filename=thisvaluechanges
key4="I hate being evaluated"
You only want to evaluate the line with filename. First test how you can select that line, something like
grep "^filename=" /home/example/.env
# or
sed -n 's/^\s*filename\s*=\s*/filename=/p' /home/example/.env
Next you can source the selected line.
source <(grep "^filename=" /home/example/.env)
When the filename is a fixed string (without $() that needs to be evaluated), you can do without source:
cp $(grep "^filename=" /home/example/.env) /home/nope/temp.txt
Before putting this in an alias, remember that a function can do everything an alias can, and can do more. You "should" stop using alias.
When you have three or four files like filename1, 2, 3, 4, you can use a function with an argument:
cpf() {
if (( $# = 0 )); then
echo "Usage: cpf filenumber"
else
cp $(grep "^filename${1}=" /home/example/.env) /home/nope/temp.txt
fi
}
You can call the function with cpf 2 for filename2.
When you want to put the filename in the environment, you can change the function
source <(grep "^filename${1}=" /home/example/.env)
My guess is that assuming /home/example/.env contains:
#!/bin/bash
# bash sourcable file
filename=$(echo 123)
then you want:
#!/bin/bash
cpf() {
(
. /home/example/.env
cp "$filename" /home/nope/temp.txt
)
}
Notes:
eval is evil. Your use of eval $(grep...) is very dangerous.
Always remember to qoute your expansions.

ls $FOLDER_PATH with space in $FOLDER_PATH: No such file or directory

I am trying to get the filename in a folder with only one file in it.
FYI: The $FOLDER_TMP contains a space in it, that is why I use printf
function nameofkeyfile(){
FOLDER_TMP="${PWD%/*/*}/folder/"
FOLDER=$(printf %q "${FOLDER_TMP}")
FILENAME=ls "$FOLDER" # Error: No such file or directory
# or this: FILENAME=$(ls "$FOLDER") # Error: No such file or directory
FNAME=`basename $FILENAME`
}
The problem is the line:
FILENAME=ls "$FOLDER" # Error: No such file or directory
Do you know why - and yes the folder is there?
And if I echo the $FOLDER it gives me the right folder.
I am trying to get the filename in a folder with only one file in it.
You definitely have the wrong approach.
Instead, consider using globbing like so:
The assignment
fname=( "${PWD%/*/*}"/folder/* )
will populate the array fname will the expansion of the given glob: that is, all files in the directory "${PWD%/*/*}"/folder/, if any. If there are no files at all, your array will contain the glob, verbatim.
Hence, a more robust approach is the following:
nameofkeyfile() {
fname=( "${PWD%/*/*}"/folder/* )
# Now check that there's at most one element in the array
if (( ${#fname[#]} > 1 )); then
echo "Oh no, there are too many files in your folder"
return 1
fi
# Now check that there is a file
if [[ ! -f ${fname[0]} ]]; then
echo "Oh no, there are no files in your folder"
return 1
fi
# Here, all is good!
echo "Your file is: $fname"
}
This uses Bash (named) arrays. If you want the function to be POSIX-compliant, it's rather straightforward since POSIX shells have an unnamed array (the positional parameters):
# POSIX-compliant version
nameofkeyfile() {
set -- "${PWD%/*/*}"/folder/*
# Now check that there's at most one element in the array
if [ "$#" -gt 1 ]; then
echo "Oh no, there are too many files in your folder"
return 1
fi
# Now check that there is a file
if [ ! -f "$1" ]; then
echo "Oh no, there are no files in your folder"
return 1
fi
# Here, all is good!
echo "Your file is: $1, I'll store it in variable fname for you"
fname=$1
}
I didn't strip the full path from the filename, but that's really easy (don't use basename for that!):1
fname=${fname##*/}
More precisely: in the Bash version, you'd use:
fname=${fname[0]##*/}
and in the POSIX version you'd use:
fname=${1##*/}
1there's a catch when using parameter expansions to get the basename, it's the case of /. But it seems you won't be in this case, so it's all safe!
To store the output ls "$FOLDER" in a variable, put it in a sub-shell:
FILENAME=$(ls "$FOLDER")
Another problem is the printf.
It adds escaping backslashes in the string,
and when you try to list the directory in the next step,
those backslashes are used literally by the shell.
So drop the printf:
function nameofkeyfile() {
FOLDER="${PWD%/*/*}/folder/"
FILENAME=$(ls "$FOLDER")
FNAME=$(basename $FILENAME)
}
Lastly, it's better to use $(...) than `...`:

Quoting parameters with spaces for later execution

I have this (test) script:
#!/bin/bash
my_cmd_bad_ ( ) {
cmd="$#"
$cmd
}
my_cmd_good_ ( ) {
"$#"
}
my_cmd_bad_ ls -l "file with space"
my_cmd_good_ ls -l "file with space"
The output is (the file does not exist, which is not the point of this question):
» ~/test.sh
ls: cannot access file: No such file or directory
ls: cannot access with: No such file or directory
ls: cannot access space: No such file or directory
ls: cannot access file with space: No such file or directory
I am surprised that the first version does not work as expected: the parameter is not quoted, and instead of processing one file, it processes three. Why?
How can I save the command that I want to execute, properly quoted? I need to execute it later, where I do not have "$#" anymore.
A simple rework of this test script would be appreciated.
See similar question: How to pass command line parameters with quotes stored in single variable?
Use those utility functions ho save a command to a string for later execution:
bash_escape() {
# backtick indirection strictly necessary here: we use it to strip the
# trailing newline from sed's output, which Solaris/BSD sed *always* output
# (unlike GNU sed, which outputs "test": printf %s test | sed -e s/dummy//)
out=`echo "$1" | sed -e s/\\'/\\''\\\\'\\'\\'/g`
printf \'%s\' "$out"
}
append_bash_escape() {
printf "%s " "$1"
bash_escape "$2"
}
your_cmd_fixed_ ( ) {
cmd="$#"
while [ $# -gt 0 ] ; do
cmd=`append_bash_escape "$cmd" "$1"` ; shift
done
$cmd
}
You can quote any single parameter and evaluate it later:
my_cmd_bad_ ( ) {
j=0
for i in "$#"; do
cmd["$j"]=\"$"$i"\"
j=$(( $j + 1 ))
done;
eval ${cmd[*]}
}
You are combining three space-delimited strings "ls", "-l", and "file with space" into a single space-delimited string cmd. There's no way to know which spaces were originally quoted (in "file with space") and which spaces were introduced during the assignment to cmd.
Typically, it is not a good idea to try to build up command lines into a single string. Use functions, or isolate the actual command and leave the arguments in $#.
Rewrite the command like this:
my_cmd_bad_ () {
cmd=$1; shift
$cmd "$#"
}
See http://mywiki.wooledge.org/BashFAQ/050
Note that your second version is greatly preferred most of the time. The only exceptions are if you need to do something special. For example, you can't bundle an assignment or redirect or compound command into a parameter list.
The correct way to handle the quoting issue requires non-standard features. Semi-realistic example involving a template:
function myWrapper {
typeset x IFS=$' \t\n'
{ eval "$(</dev/fd/0)"; } <<-EOF
for x in $(printf '%q ' "$#"); do
echo "\$x"
done
EOF
}
myWrapper 'foo bar' $'baz\nbork'
Make sure you understand exactly what's going on here and that you really have a good reason for doing this. It requires ensuring side-effects can't affect the arguments. This specific example doesn't demonstrate a very good use case because everything is hard-coded so you're able to correctly escape things in advance and expand the arguments quoted if you wanted.

Bash - if and for statements

I am little unfamiliar with the 'if...then...fi' and the 'for' statements syntax.
Could anyone explain what the "$2/$fn" and "/etc/*release" in the code snippets below mean?...specifically on the use of the forward slash....and the asterisk...
if [ -f "$filename" ]; then
if [ ! -f "$2/$fn" ]; then
echo "$fn is missing from $2"
missing=$((missing + 1))
fi
fi
and
function system_info
{
if ls /etc/*release 1>/dev/null 2>&1; then
echo "<h2>System release info</h2>"
echo "<pre>"
for i in /etc/*release; do
# Since we can't be sure of the
# length of the file, only
# display the first line.
head -n 1 $i
done
uname -orp
echo "</pre>"
fi
} # end of system_info
...thx for the help...
/etc/*release : here the * will match any number of any characters, so any thing /etc/0release , /etc/asdfasdfr_release etc will be matched. Simply stated, it defined all the files in the /etc/ directory which ends with the string release.
The $2 is the 2nd commandline argument to the shell script, and $fn is some other shell variable. The "$2/$fn" after the variable substitutions will make a string, and the [ -f "$2/$fn" ] will test if the string formed after the substitution forms a path to a regular file which is specified by the -f switch. If it is a regular file then the body of if is executed.
In the for loop the loop will loop for all the files ending with the string release in the directory /etc (the path). At each iteration i will contain the next such file name, and for each iteration the first 1 line of the file is displayed with the head command by getting the file name from variable i within the body.
It is better to check the manual man bash and for if condition check man test . Here is a good resource: http://tldp.org/LDP/Bash-Beginners-Guide/html/
The forward slash is the path separator, and the * is a file glob character. $2/$fn is a path where $2 specifies the directory and $fn is the filename. /etc/*release expands to the space separated list of all the files in /etc whose name ends in "release"
Dollar sign marks variable. The "-f" operator means "file exsists".
So,
[ -f "$filename" ]
checks if there is file named the same as value contained in $filename variable.
Simmilar, if we assume that $2 = "some_folder", and $fn = "some_file", expression
[ ! -f "$2/$fn" ]
returns true if file some_folder/some_file doesn't exsist.
Now, about asterisk - it marks "zero or more of any character(s)". So, expression:
for i in /etc/*release; do
will iterate trough all folders named by that pattern, for example:
/etc/release, /etc/666release, /etc/wtf_release...
I hope this helps.

Resources