Command not found with first positional argument used more than once - bash

I have a script in bash which basically creates a user and install all the necessary applications.
It works the way that it iterates through a couple of commands, where I put a variable at the end of the command (positional argument).
I've set it up this way
function Install
{
COMMANDS=(
"Do_1st_thing $1"
"Do_2nd_thing $1"
"Do_3rd_thing $1"
)
for CMD in "${COMMANDS[#]}" ; do
$CMD
done
}
Then I run it
Install first_argument
The problem is that the first command is successful, however every next command says "Command not found".
Does the first positional argument ($1) changes after the execution of the first command?
Would I have to "eval" the "$CMD" in the "for loop" to get it working?
Feel free to ask any question, I will do my best to answer them.
Thank you for your help,
Kris

You are declaring an array with the first argument hard-coded in. If $1 is "foo" you are declaring
COMMANDS=(
"Do_1st_thing foo"
"Do_2nd_thing foo"
"Do_3rd_thing foo"
)
Storing these commands in an array seems like a weird thing to do anyway. Just
Install () {
Do_1st_thing "$#"
Do_2nd_thing "$#"
Do_3rd_thing "$#"
}
If your commands don't all accept the same arguments, you need to refactor the code, but that seems to be outside the scope of your concrete question here.
If they do, you might also consider refactoring into
commands=(Do_1st_thing Do_2nd_thing Do_3rd_thing)
for cmd in "${commands[#]}"; do
"$cmd" "$#"
done
(Notice also Correct Bash and shell script variable capitalization)
Maybe see also http://mywiki.wooledge.org/BashFAQ/050

As this is a bash function, you don't need the word function to designate it as a function. You would therefore write the code as below:
#!/bin/bash
Install()
{
COMMANDS=(
"ls $1"
"stat $1"
"file $1"
)
for CMD in "${COMMANDS[#]}" ; do
$CMD
done
}
Install testfile.txt

Related

bash "$#" not working with arguments starting with '-'

I am working on an option driven bash script that will use getopts. The script has cases where it can accept multiple options and specific cases where only one option is accepted. While testing a few cases out I ran into this issue which I have reduced down to pseudo-code for this question.
for arg in "$#"; do
echo ${arg}
done
echo "end"
Running below returns:
$ ./test.sh -a -b
-a
end
I am running bash 4.1.2, why isn't the -b returned on the empty line? I assume this has to do with the '-'.
I cannot reproduce your exact error, but this is the risk of using echo: if $arg is a valid option, it will be treated as such, not a string to print. Use printf instead:
printf '%s\n' "$arg"
Also check if you have applied any "shift" commands that might remove the arguments before you test then (typical in a argument collection block that might include a case statement)

Passing multiple commands to script calling time

I have a bash script let's say foo.sh that in this minimal example looks like this:
#!/bin/bash
function __measure_time {
time "$#"
}
__measure_time "$*"
What I want to do now is pass two commands to this script that are supposed to be run after another and I want to measure the time. So basically I am doing something like:
./foo.sh bash -c "echo 'ss' && sleep 1"
But that doesn't work the way I want it to. I don't get the 'ss' from the echo and the sleep is basically being ignored. Is there a way to make this work?
If you want the arguments to pass through correctly, you need to call __measure_time with "$#", not "$*":
#!/bin/bash
__measure_time() { #why the `function` syntax when you can be POSIX?
time "$#"
}
__measure_time "$#"
"$*" joins all arguments on the first character of $IFS into a string.
"$#" is magic for "give me all the arguments, as if each was separately quoted."

Passing argument to script invoked by exec producing undesired result

I'm trying to pass an argument to a shell script via exec, within another shell script. However, I get an error that the script does not exist in the path - but that is not the case.
$ ./run_script.sh
$ blob has just been executed.
$ ./run_script.sh: line 8: /home/s37syed/blob.sh test: No such file or directory
For some reason it's treating the entire execution as one whole absolute path to a script - it isn't reading the string as an argument for blob.sh.
Here is the script that is being executed.
#!/bin/bash
#run_script.sh
blobPID="$(pgrep "blob.sh")"
if [[ -z "$blobPID" ]]
then
echo "blob has just been executed."
#execs as absolute path - carg not read at all
( exec "/home/s37syed/blob.sh test" )
#this works fine, as exepcted
#( exec "/home/s37syed/blob.sh" )
else
echo "blob is currently running with pid $blobPID"
ps $blobPID
fi
And the script being invoked by run_script.sh, not doing much, just emulating a long process/task:
#!/bin/bash
#blob.sh
i=0
carg="$1"
if [[ -z "$carg" ]]
then
echo "nothing entered"
else
echo "command line arg entered: $carg"
fi
while [ $i -lt 100000 ];
do
echo "blob is currently running" >> test.txt
let i=i+1
done
Here is the version of Bash I'm using:
$ bash --version
GNU bash, version 4.2.37(1)-release (x86_64-pc-linux-gnu)
Any advice/comments/help on why this is happening would be much appreciated!
Thanks in advance,
s37syed
Replace
exec "/home/s37syed/blob.sh test"
(which tries to execute a command named "/home/s37syed/blob.sh test" with no arguments)
by
exec /home/s37syed/blob.sh test
(which executes "/home/s37/syed/blob.sh" with a single argument "test").
Aside from the quoting problem Cyrus pointed out, I'm pretty sure you don't want to use exec. What exec does is replace the current shell with the command being executed (rather than running the command as a subprocess, as it would without exec). Putting parentheses around it makes it execute that section in a subshell, thus effectively cancelling out the effect of exec.
As chepner said, you might be thinking of the eval command, which performs an extra parsing pass before executing the command. But eval is a huge bug magnet. It's incredibly easy to use eval in unsafe ways (see BashFAQ #48). If you need to construct a command, see BashFAQ #50 for better ways to do it.

How to quote parameters when calling a shell script from within another script using a variable

I have two shell scripts. One script dynamically creates the call to the second script, based on the parameter it received, and then executes the call.
My problem is that the parameters the first script gets may contain spaces, so I must quote the parameter in the call to script2.
This is an example to the problem:
script1.sh:
#!/bin/sh
param=$1
command="./script2.sh \"$param\""
echo $command
$command
script2.sh:
#!/bin/sh
param=$1
echo "the value of param is $param"
When I run:
./script1.sh "value with spaces"
I get:
./script2.sh "value with spaces"
the value of param is "value
Which is of course not what I need.
What is wrong here??
TIA.
EDIT :
I found the solution thanks to the useful link in tripleee's comment. Here it is in case it helps anybody.
In short, in order to solve this, one should use an array for the arguments.
script1.sh:
#!/bin/sh
param=$1
args=("$param")
script_name="./script2.sh"
echo $script_name "${args[#]}"
$script_name "${args[#]}"
Use "$#" to refer to all command-line parameters with quoting intact.

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