Why the echo failed? - bash

I want to create many new bash scripts through this bash. Here is my code.
#!/bin/bash
# create bash shell automatically
for file in "$*"
do
if [ ! -e "$file" ]
then
touch $file
chmod u+x $file
echo "#!/bin/bash" >> $file
echo "# " >> $file
echo "create success"
fi
done
if [ $# \> 1 ]
then
echo "$# shell files are created!"
else
echo "$# shell is created!"
fi
When I run this script like this:
./create_shell test1 test2 test3
the terminal said:
"line9:ambiguous redirect"
"line10:ambiguous redirect"
What does that mean?

The problem came from the use of $*, whether you quote or unquote it. The correct way to iterate through positional parameter is using "$#", notice the double quote.
for file in "$#"; do
: ...
done
or even POSIXly:
for file do
: ...
done
You got the error message from bash, because bash see the content of $file was not expanded to one word, it was expanded to three separated words test1, test2, test3. Try:
a='1 2 3'
echo 1 >> $a
to see what will happen.
Not to mention that you spoil the sole reason for using "$#". In order for this construct to work, you must also put double quotes around the variables that derive from it, if you don't want the split+glob operators to be invoked. Leaving variables unquote will lead to many security implications.

The '$*' is a string not the parameter array. You can do it like this
fileArr=($*)
for file in ${fileArr[#]}
do you things

The quotes around "$*" will prevent bash from parsing the passed parameter into individual tokens. The loop will be executed once with all of the parameters passed (i.e. if you ran test.sh one two three then the echo command will try to redirect to "one two three"). Just remove the quotes.

Related

How to pass a list of files with a particular extension in a for loop in bash

First of all, hi to everyone, that's my first post here.
I swear I have checked the site for similar questions to avoid the "double post about same argument" issue but none of them answered exactly to my question.
The problem is that in the code below I always get the "There are no files with this extension" message when I call the script passing it an extension as first argument.
#!/bin/bash
if [ "$1" ];
then
file=*."$1";
if [ -f "$file" ];
then
for i in "$file";
[...do something with the each file using "$i" like echo "$i"]
else
echo "There are no files with this extension";
fi;
else
echo "You have to pass an extension"
fi;
I tried using the double parenthesis, using and not using the quotes in the nested if, using *."$1" directly in the if, but none of this solution worked.
One problem is that you're not quoting a variable when you first assign a value to file. In this statement:
file=*."$1";
The * will be interpreted by the shell, so for example if you passed in .py on the command line, file might end up with the value file1.py file2.py, which will throw off your file existence test later on.
Another problem, as #sideshowbarker points out, is that you can't use wildcards with the [ -f ... ].
Another variable quoting issue is that quoting inhibits wildcard expansion, such that even without the file existence test, if $file is, e.g., *.txt, then this:
for x in "$file"; do ...
Will loop over a single argument with the literal value *.txt, while this:
for x in $file; do ...
Will loop over all files that end with a .txt extension (unless there are none, in which case it will loop once with $x set to the literal value *.txt).
Typically, you would write your script to expect a list of arguments, and allow the user to call it like myscript *.txt...that is, leave wildcard handling to the interactive shell, and just let your script process a list of arguments. Then it becomes simply:
for i in "$#"; do
echo do something with this file named "$x"
done
If you really want to handle the wildcard expansion in your script, something like this might work:
#!/bin/bash
if [ "$1" ];
then
ext="$1"
for file in *.$ext; do
[ -f "$file" ] || continue
echo $file
done
else
echo "You have to pass an extension"
fi;
The statement [ -f "$file" ] || continue is necessary there because of the case I mentioned earlier: if there are no files, the loop will still execute once with the literal expansion of *.$ext.

How to call an application in Bash using an argument with spaces

I have a bash file which is passed arguments which contain spaces. The bash file looks like:
#!/bin/bash
another_app "$1"
However, instead of processing the argument as a single argument, as I believe it should, it processes it as a number of arguments depending on how many spaces. For example, if I call my bash file such:
my_app "A Number Of Words"
Then the "another_app" application gets passed 4 different arguments, instead of one. How can I just pass the single argument through to the second application?
The others are correct it will depend somewhat on how the 2nd app handles the args. You can also have a little control as to how the args are passed. You can do this with some quoting or using the "$#" var as mentioned by #steve
For example app1.sh
#!/bin/bash
echo "Argument with no quotes"
./another_app.sh $1
echo "Argument with quotes"
./another_app.sh "$1"
echo "Argument with \$#"
./another_app.sh "$#"
and another_app.sh
#!/bin/bash
echo "Inside $0"
echo "Number of args passed to me: $#"
for X in "${#}"
do
echo $X
done
echo "Exiting $0"
Call the second application using "$#":
#!/bin/bash
another_app "$#"

Capturing verbatim command line (including quotes!) to call inside script

I'm trying to write a "phone home" script, which will log the exact command line (including any single or double quotes used) into a MySQL database. As a backend, I have a cgi script which wraps the database. The scripts themselves call curl on the cgi script and include as parameters various arguments, including the verbatim command line.
Obviously I have quite a variety of quote escaping to do here and I'm already stuck at the bash stage. At the moment, I can't even get bash to print verbatim the arguments provided:
Desired output:
$ ./caller.sh -f -hello -q "blah"
-f hello -q "blah"
Using echo:
caller.sh:
echo "$#"
gives:
$ ./caller.sh -f -hello -q "blah"
-f hello -q blah
(I also tried echo $# and echo $*)
Using printf %q:
caller.sh:
printf %q $#
printf "\n"
gives:
$ ./caller.sh -f hello -q "blah"
-fhello-qblah
(I also tried print %q "$#")
I would welcome not only help to fix my bash problem, but any more general advice on implementing this "phone home" in a tidier way!
There is no possible way you can write caller.sh to distinguish between these two commands invoked on the shell:
./caller.sh -f -hello -q "blah"
./caller.sh -f -hello -q blah
There are exactly equivalent.
If you want to make sure the command receives special characters, surround the argument with single quotes:
./caller.sh -f -hello -q '"blah"'
Or if you want to pass just one argument to caller.sh:
./caller.sh '-f -hello -q "blah"'
You can get this info from the shell history:
function myhack {
line=$(history 1)
line=${line#* }
echo "You wrote: $line"
}
alias myhack='myhack #'
Which works as you describe:
$ myhack --args="stuff" * {1..10} $PATH
You wrote: myhack --args="stuff" * {1..10} $PATH
However, quoting is just the user's way of telling the shell how to construct the program's argument array. Asking to log how the user quotes their arguments is like asking to log how hard the user punched the keys and what they were wearing at the time.
To log a shell command line which unambiguously captures all of the arguments provided, you don't need any interactive shell hacks:
#!/bin/bash
line=$(printf "%q " "$#")
echo "What you wrote would have been indistinguishable from: $line"
I understand you want to capture the arguments given by the caller.
Firstly, quotes used by the caller are used to protect during the interpretation of the call. But they do not exist as argument.
An example: If someone call your script with one argument "Hello World!" with two spaces between Hello and World. Then you have to protect ALWAYS $1 in your script to not loose this information.
If you want to log all arguments correctly escaped (in the case where they contains, for example, consecutive spaces...) you HAVE to use "$#" with double quotes. "$#" is equivalent to "$1" "$2" "$3" "$4" etc.
So, to log arguments, I suggest the following at the start of the caller:
i=0
for arg in "$#"; do
echo "arg$i=$arg"
let ++i
done
## Example of calls to the previous script
#caller.sh '1' "2" 3 "4 4" "5 5"
#arg1=1
#arg2=2
#arg3=3
#arg4=4 4
#arg5=5 5
#Flimm is correct, there is no way to distinguish between arguments "foo" and foo, simply because the quotes are removed by the shell before the program receives them. What you need is "$#" (with the quotes).

All arguments into files with correct quoting using "$#"

I need my bashscript to cat all of its parameters into a file. I tried to use cat for this because I need to add a lot of lines:
#!/bin/sh
cat > /tmp/output << EOF
I was called with the following parameters:
"$#"
or
$#
EOF
cat /tmp/output
Which leads to the following output
$./test.sh "dsggdssgd" "dsggdssgd dgdsdsg"
I was called with the following parameters:
"dsggdssgd dsggdssgd dgdsdsg"
or
dsggdssgd dsggdssgd dgdsdsg
I want neither of these two things: I need the exact quoting which was used on the command line. How can I achieve this? I always thought $# does everything right in regards to quoting.
Well, you are right that "$#" has the args including the whitespace in each arg. However, since the shell performs quote removal before executing a command, you can never know how exactly the args were quoted (e.g. whether with single or double quotes, or backslashes or any combination thereof--but you shouldn't need to know, since all you should care for are the argument values).
Placing "$#" in a here-document is pointless because you lose the information about where each arg starts and ends (they're joined with a space inbetween). Here's a way to see just this:
$ cat test.sh
#!/bin/sh
printf 'I was called with the following parameters:\n'
printf '"%s"\n' "$#"
$ ./test.sh "dsggdssgd" "dsggdssgd dgdsdsg"
I was called with the following parameters:
"dsggdssgd"
"dsggdssgd dgdsdsg"
Try:
#!/bin/bash
for x in "$#"; do echo -ne "\"$x\" "; done; echo
To see what's interpreted by Bash, use:
bash -x ./script.sh
or add this to the beginning of your script:
set -x
You might want add this on the parent script.

Bash: pass variable as a single parameter / shell quote parameter

I'm writing a bash script which has to pass a variable to another program:
./program $variable
The problem is, it is absolutely necessary for $variable to be passed as a single parameter, which isn't the case if it contains whitespace.
variable=Hello World
./program $variable
-> program receives two arguments: 'Hello' 'World'
Quoting it doesn't do anything at all (well done, bash devs):
variable=Hello World
./program "$variable"
-> program receives: 'Hello' 'World'
Double quoting it does crazy stuff (well done, bash devs):
variable=Hello World
./program "'$variable'"
-> program receives: "'Hello" "World'"
Is there an easy way to do this? Heck, is there a way to do this at all?
Update: Okay, since the problem doesn't seem to be bash, here's some additional info.
The program I'm passing arguments to is a python script. Without modifying the arguments in any way, I get
print sys.argv
-> ['/usr/bin/program', "'Hello", "World'"]
How can I fix that?
Edit: No, I haven't tried
variable="Hello World"
because I never declare $variable. It's not being declared inside my bash function and I'm not allowed to modify it.
Edit: Okay, I got it to work that way.
local temp="$variable"
./program "$temp"
I'd like to know why it works that way and not any other way, though.
did you try with var="hello world"?
i tried this in my solaris box.
> setenv var "hello world"
> cat temp.sh
#!/bin/sh
echo $1
echo $2
> ./temp.sh "$var"
hello world
>
as you can see the $2 is not printed.$var is considered as only one argument.
When you call your script pass the arguments within quotes.
Example script:
#!/bin/bash
for arg in "$#"; do
echo "arg: $1";
shift;
done
When you call it with:
./program "parameter with multiple words" parameter2 parameter3 "another parameter"
The output should be:
arg: parameter with multiple words
arg: parameter2
arg: parameter3
arg: another parameter
Have a look on http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html .
The problem is that the expansion of variables is done before of the command line parameters hence your behavior.
You might work it arround with setting IFS to something weird as
IFS='###' V='foo bar baz'; ./program $V
The problem seems to be inside the "program"
variable="Hello World" # quotes are needed because of the space
./program "$variable" # here quotes again
and inside the program
echo "program received $# arguments:"
i=1
for arg in "$#" # again quotes needed
do echo "arg $((i=i+1)): '$arg'" # and again
done
This is almost certainly a problem in the way you are reading the variable in your program.
For instance suppose this is your script (just one line for testing):
echo "$1"
Let's call it echo.sh. If you run echo.sh "test best", you will get test best.
But if your program says
echo $1
you might get behaviour like what you're seeing.

Resources