We've got a script running the last 2 commands. But it's failing..
$ if [ -d / ]; then echo a; else echo b; fi
a
$ bash -c 'if [ -d / ]; then echo a; else echo b; fi'
a
$ A="bash -c 'if [ -d / ]; then echo a; else echo b; fi'"
$ $A
[: -c: line 0: unexpected EOF while looking for matching `''
[: -c: line 1: syntax error: unexpected end of file
I really wonder why? Thanks!
In your last command line, you have assigned a value to the variable A, but failed to evaluate it before calling $A. Instead, any of the following will work:
Define the variable and then evaluate it,
~]# A="bash -c 'if [ -d / ]; then echo a; else echo b; fi'"
~]# eval $A
OR evaluate it first and then echo it.
~]# A=$(bash -c 'if [ -d / ]; then echo a; else echo b; fi')
~]# echo $A
OR not personally preferred, you can also use this:
~]# A="eval bash -c 'if [ -d / ]; then echo a; else echo b; fi'"
~]# $A
All of the above will result in a.
Note: See why you shouldn't use the third option: http://mywiki.wooledge.org/BashFAQ/048
Let's take a simpler example to explain:
$ A="bash -c 'echo hello'"
$ $A
hello': -c: line 0: unexpected EOF while looking for matching `''
hello': -c: line 1: syntax error: unexpected end of file
When executing $A,
the shell performs word splitting on the value of $A.
It finds these words:
bash
-c
'echo
hello'
You probably expected that 'echo hello' would be evaluated to the value echo hello (without the single-quotes), but that is not the case.
It cannot happen.
When you set the value of A with A="...",
any '...' embedded within the "..." are not evaluated,
so the literal ' remain in place.
It would be dangerous to recursively evaluate string values like that.
So after the word splitting, what gets executed looks more like this:
bash -c "'echo" "hello'"
So then bash tries to execute the command 'echo, and it fails,
because it doesn't find a matching '.
As for what is the hello': prefix of the error messages,
I believe this extract from man bash explains:
SYNOPSIS
bash [options] [command_string | file]
...
-c If the -c option is present, then commands are read from the
first non-option argument command_string. If there are argu-
ments after the command_string, the first argument is
assigned to $0 and any remaining arguments are assigned to
the positional parameters. The assignment to $0 sets the
name of the shell, which is used in warning and error mes-
sages.
As another example, in the original code in the question,
the error messages were prefixed with [:, because after the word splitting of "bash -c 'if [ -d / ]; then echo a; else echo b; fi'",
the next word after 'if is [.
If you really want to do something like this, you could use an array instead:
A=(bash -c 'if [ -d / ]; then echo a; else echo b; fi')
"${A[#]}"
By writing this way, A will have 3 values:
bash
-c
if [ -d / ]; then echo a; else echo b; fi
And these will be used as 3 words when executing,
producing the expected output a.
Related
I am to write a basg=h script that displays a message if you input the correct number (i.e. 1 = function f1, 2 = function f2, and 3 = function f3)
My code is as follow
#!/bin/bash
function f1
{
echo "This message is from function 1"
}
function f2
{
echo "This messge is from function 2"
}
function f3
{
echo "This message is from function 3"
}
function=$(typeset -F)
declare -a myarr=(`echo "$function" [sed 's/declare[ ]-f / /g'`)
read -p "Enter a number (1, 2, or 3): " number
if ! [[ "$number" =~ ^[0-9]+$ ]]
then
echo "Not a valid number"
exit
fi
flag=0
for element in "${myarr[#]}
do
if echo "$element" | grep -q "$num"; then
$element
flag=1
fi
done
if [ "$flag" -eq 0 ]; then
echo "No function matches number $num"
fi
Now when I run the code I obtain the error
q6: line 43: unexpected EOF while looking for matching `"'
q6: line 45: syntax error: unexpected end of file
Any help sourcing the errors?
for element in "${myarr[#]}
Missing the end quote. You can catch errors like this by seeing where the syntax highlighting goes wonky. Notice how much of the script following this line is incorrectly colored red?
Even better, you can use ShellCheck:
Line 32:
for element in "${myarr[#]}
^-- SC1009: The mentioned syntax error was in this for loop.
^-- SC1078: Did you forget to close this double quoted string?
Fix that and you get:
Line 1:
#!/bin/bash
^-- SC1114: Remove leading spaces before the shebang.
Line 20:
declare -a myarr=(`echo "$function" [sed 's/declare[ ]-f / /g'`)
^-- SC2207: Prefer mapfile or read -a to split command output (or quote to avoid splitting).
^-- SC2006: Use $(..) instead of legacy `..`.
^-- SC2116: Useless echo? Instead of 'cmd $(echo foo)', just use 'cmd foo'.
Line 22:
read -p "Enter a number (1, 2, or 3): " number
^-- SC2162: read without -r will mangle backslashes.
Line 35:
if echo "$element" | grep -q "$num"; then
^-- SC2154: num is referenced but not assigned.
my script check if the arguments are files or folders
if it is a file, he count the number of lines
after that, if the number of lines is great then 20 or less he do some instructions
the problem is in this instructionn= cat $a | wc -l
My script:
#!/usr/bin/env bash
echo 'Hello this is the test of' `date`
echo 'arguments number is ' $#
if [ $# -eq 4 ]
then
for a in $#
do
if [ -d $a ]
then
ls $a > /tmp/contenu
echo "contenu modified"
elif [ -f $a ]
then
# this instruction must set a numeric value into n
echo "my bad instruction"
n= cat $a | wc -l
echo "number of lines = " $n
# using the numeric value in a test (n must be numeric and takes the number of lines in the current file)
if [ $n -eq 0 ]
then
echo "empty file"
elif [ $n -gt 20 ]
then
echo ` head -n 10 $a `
else
cat $a
fi
else
echo "no file or directory found"
fi
done
else
echo "args number must be 4"
fi
This is the output of the execution of the incorrect instruction
my bad instruction
5
number of lines =
ExamenEx2.sh: line 19: [: -eq : opérateur unaire attendu
The line n= cat $a | wc -l is an offending instruction. Always remember that bash shell scripting is extremely case-sensitive. Your command is interpreted by the shell as having to run two separate commands
n= cat $a | wc -l
#^^ ^^^^^^^^^^^^^^
#1 2
The first part just stores an empty string to the variable n and the next prints the line count of the file stored in variable a. Notice that the shell does not throw errors for this. Because it is not violating the syntax (just the semantics are wrong). But the line count is never assigned to the variable n.
The error is seen when the conditional if [ $n -eq 0 ] is hit when you are doing a comparison with an empty variable on the LHS.
You wanted to run a command and store its output, you need command-substitution($(..)) for that. Assuming the $a contains a name of a file just do
n=$(wc -l < "$a")
Note, that I've removed the useless cat usage and piping it to wc. But wc can read from an input stream directly.
Also note that you have multiple bad practices in your script. Remember to do the following
Always double-quote the shell variables - "$#", "$#", [ -f "$a" ], [ -d "$a" ]
Don't use the `` for command-substitution, because it is not easily nestable and you might have issues related to quoting also.
You can use conditional expression [[ if you are sure if the script is running under bash in which a variable containing spaces can be used without quoting on the LHS
My shell name is test.sh, and I want to output all parameters which are passed to test.sh , but I find my code can't work well.
#!/bin/bash
i=1
num=$#
while [ $i -le $num ]; do
echo $($i)
((i++))
done
When I run ./test.sh -a -b -c, my expected output is:
-a
-b
-c
but, it tells me
./test.sh: line 5: 1: command not found
./test.sh: line 5: 2: command not found
./test.sh: line 5: 3: command not found
How can I resolve this issue and output all parameters using echo command?
You are looking for variable indirection:
#!/bin/bash
i=1
num=$#
while [ $i -le $num ]; do
echo ${!i} # <--- print the content of $1, $2...
((i++))
done
Upon execution, this returns:
$ bash test.sh -a -b "-c d"
-a
-b
-c d
From Bash Reference Manual → 3.5.3 Shell Parameter Expansion:
If the first character of parameter is an exclamation point (!), and parameter is not a nameref, it introduces a level of variable indirection. Bash uses the value of the variable formed from the rest of parameter as the name of the variable; this variable is then expanded and that value is used in the rest of the substitution, rather than the value of parameter itself. This is known as indirect expansion. If parameter is a nameref, this expands to the name of the variable referenced by parameter instead of performing the complete indirect expansion. The exceptions to this are the expansions of ${!prefix*} and ${!name[#]} described below. The exclamation point must immediately follow the left brace in order to introduce indirection.
If you wish to make it more verbose, show the mapping of ${1..n} to its value:
#!/bin/bash
i=1
num=$#
while [ $i -le $num ]; do
printf "$%s = %s\n" "$i" "${!i}"
((i++))
done
See the output:
$ bash test.sh -a -b "-c d"
$1 = -a
$2 = -b
$3 = -c d
In Bash the sequence $(...) is to create a sub-shell to execute a command. It's a "new" way of using backticks.
The expression
$($i)
is equivalent to
`$i`
So what you are doing is calling the arguments as a command in a sub-shell.
You could also use a one-line script
#!/bin/bash
echo $*
I'd like to add an argument to a command in bash only if a variable evaluates to a certain value. For example this works:
test=1
if [ "${test}" == 1 ]; then
ls -la -R
else
ls -R
fi
The problem with this approach is that I have to duplicate ls -R both when test is 1 or if it's something else. I'd prefer if I could write this in one line instead such as this (pseudo code that doesn't work):
ls (if ${test} == 1 then -la) -R
I've tried the following but it doesn't work:
test=1
ls `if [ $test -eq 1 ]; then -la; fi` -R
This gives me the following error:
./test.sh: line 3: -la: command not found
A more idiomatic version of svlasov's answer:
ls $( (( test == 1 )) && printf %s '-la' ) -R
Since echo understands a few options itself, it's safer to use printf %s to make sure that the text to print is not mistaken for an option.
Note that the command substitution must not be quoted here - which is fine in the case at hand, but calls for a more robust approach in general - see below.
However, in general, the more robust approach is to build up arguments in an array and pass it as a whole:
# Build up array of arguments...
args=()
(( test == 1 )) && args+=( '-la' )
args+=( '-R' )
# ... and pass it to `ls`.
ls "${args[#]}"
Update: The OP asks how to conditionally add an additional, variable-based argument to yield ls -R -la "$PWD".
In that case, the array approach is a must: each argument must become its own array element, which is crucial for supporting arguments that may have embedded whitespace:
(( test == 1 )) && args+= ( '-la' "$PWD" ) # Add each argument as its own array element.
As for why your command,
ls `if [ $test -eq 1 ]; then -la; fi` -R
didn't work:
A command between backticks (or its modern, nestable equivalent, $(...)) - a so-called command substitution - is executed just like any other shell command (albeit in a sub-shell) and the whole construct is replaced with the command's stdout output.
Thus, your command tries to execute the string -la, which fails. To send it to stdout, as is needed here, you must use a command such as echo or printf.
Print the argument with echo:
test=1
ls `if [ $test -eq 1 ]; then echo "-la"; fi` -R
I can't say how acceptable this is, but:
test=1
ls ${test:+'-la'} -R
See https://stackoverflow.com/revisions/16753536/1 for a conditional truth table.
Another answer without using eval and using BASH arrays:
myls() { local arr=(ls); [[ $1 -eq 1 ]] && arr+=(-la); arr+=(-R); "${arr[#]}"; }
Use it as:
myls
myls "$test"
This script builds whole command in an array arr and preserves the original order of command options.
I wrote a bash script that uploads a file on my home server. It gets activated from a folder action script using applescript. The setup is the folder on my desktop is called place_on_server. Its supposed to have an internal file structure exactly like the folder I want to write to: /var/www/media/
usage goes something like this:
if directory etc added to place_on_server: ./upload DIR etc
if directory of directory: etc/movies ./upload DIR etc movies //and so on
if file to place_on_server: ./upload F file.txt
if file in file in place_on_server ./upload F etc file.txt //and so on
for creating a directory its supposed to execute a command like:
ssh root#192.168.1.1<<EOF
cd /var/www/media/wherever
mkdir newdirectory
EOF
and for file placement:
rsync -rsh='ssh -p22' file root#192.168.1.1:/var/www/media/wherever
script:
#!/bin/bash
addr=$(ifconfig -a | ./test)
if ($# -le "1")
then
exit
elif ($1 -eq "DIR")
then
f1="ssh -b root#$addr<<EOF"
list = "cd /var/www/media\n"
if($# -eq "2")
then
list=list+"mkdir $2\nEOF\n"
else
num=2
i=$(($num))
while($num < $#)
do
i=$(($num))
list=list+"mkdir $i\n"
list=list+"cd $i\n"
$num=$num+1
done
fi
echo $list
elif ($1 -eq "F")
then
#list = "cd /var/www/media\n"
f2="rsync -rsh=\'ssh -p22\' "
f3 = "root#$addr:/var/www/media"
if($# -eq "2")
then
f2=f2+$2+" "+f3
else
num=3
i=$(($num))
while($num < $#)
do
i=$(($num))
f2=f2+"/"+$i
$num=$num+1
done
i=$(($num))
f2=f2+$i+" "+$f3
fi
echo $f2
fi
exit
output:
(prompt)$ ./upload2 F SO test.txt
./upload2: line 3: 3: command not found
./upload2: line 6: F: command not found
./upload2: line 25: F: command not found
So as you can see I'm having issues handling input. Its been awhile since I've done bash. And it was never extensive to begin with. Looking for a solution to my problem but also suggestions. Thanks in advance.
For comparisons, use [[ .. ]]. ( .. ) is for running commands in subshells
Don't use -eq for string comparisons, use =.
Don't use < for numerical comparisons, use -lt
To append values, f2="$f2$i $f3"
To add line feeds, use $'\n' outside of double quotes, or a literal linefeed inside of them.
You always need "$" on variables in strings to reference them, otherwise you get the literal string.
You can't use spaces around the = in assignments
You can't use $ before the variable name in assignments
To do arithmetics, use $((..)): result=$((var1+var2))
For indirect reference, such as getting $4 for n=4, use ${!n}
To prevent word splitting removing your line feeds, double quote variables such as in echo "$line"
Consider writing smaller programs and checking that they work before building out.
Here is how I would have written your script (slightly lacking in parameter checking):
#!/bin/bash
addr=$(ifconfig -a | ./test)
if [[ $1 = "DIR" ]]
then
shift
( IFS=/; echo ssh "root#$addr" mkdir -p "/var/www/media/$*"; )
elif [[ $1 = "F" ]]
then
shift
last=$#
file=${!last}
( IFS=/; echo rsync "$file" "root#$addr:/var/www/media/$*" )
else
echo "Unknown command '$1'"
fi
$* gives you all parameters separated by the first character in $IFS, and I used that to build the paths. Here's the output:
$ ./scriptname DIR a b c d
ssh root#somehost mkdir -p /var/www/media/a/b/c/d
$ ./scriptname F a b c d somefile.txt
rsync somefile.txt root#somehost:/var/www/media/a/b/c/d/somefile.txt
Remove the echos to actually execute.
The main problem with your script are the conditional statements, such as
if ($# -le "1")
Despite what this would do in other languages, in Bash this is essentially saying, execute the command line $# -le "1" in a subshell, and use its exit status as condition.
in your case, that expands to 3 -le "1", but the command 3 does not exist, which causes the error message
./upload2: line 3: 3: command not found
The closest valid syntax would be
if [ $# -le 1 ]
That is the main problem, there are other problems detailed and addressed in that other guy's post.
One last thing, when you're assigning value to a variable, e.g.
f3 = "root#$addr:/var/www/media"
don't leave space around the =. The statement above would be interpreted as "run command f3 with = and "root#$addr:/var/www/media" as arguments".