I'm studyng Bash, and I see that the form
C=example
echo "$C"
give the same result of the form
C="example"
echo $C
I'd like to know if is better put the " " in the assignment of the variable or after the $. Or if it is indifferent. or if one is consider "more beautiful" than the other.
If you're certain that a variable's value is a single word (no white space) then it's OK to use $varname or ${varname}. If you can't guarantee this, then you should use "$varname" or "${varname}". Note that bash does word-splitting before interpreting your command, so you may actually get a syntax error if you don't quote the expression, for example
C="white space"
if [ -z $C ]
then
...
fi
will result in syntax error:
-bash: [: white: binary operator expected
while this works fine:
C="white space"
if [ -z "$C" ]
then
...
fi
This is due to the fact after variable expansion in the first, unquoted case bash sees this:
if [ -z white space ]
then
...
fi
and the -z operator expects just one, not two arguments. In the second, quoted case bash sees this:
if [ -z "white space" ]
then
...
fi
i.e. just a single argument as required. Note also that quotes were used in assignment
C="white space"
as it would also produce an error if you wrote
C=white space
since this would mean: execute command space with environment containing an added variable C=white.
So, in general you should quote these expressions to ensure your code is more robust against unforeseen variable values. This is especially true if the variable value comes from input, file etc. It is usually safe to drop the quotes for integer variables or when you just want to display the value of a variable as in echo $C.
It matters when the string contains whitespace characters. Without the quotes, whitespace characters are treated as token delimiters and bash tries to interpret the substituted string as an expression.
Always put quotes to be safe, when you don't intend to evaluate the variable as a part of the expression.
Imagine you change the input from "example" to "two words", then you could encounter strange behaviour or even syntax errors when executing the script, in case you have overlooked the above.
In other words,
C="abc def"
# the echo command receives one argument: "abc def"
echo "$C"
# echo receives two arguments: "abc" and "def"
echo $C
# bash tries to execute the program "abc" with a first argument "def"
$C
# bash tries to execute the program "abc def"
"$C"
A good documentation about quotes and word-spliting :
"USE MORE QUOTES!" They are vital. Also, learn the difference between ' and " and `. See http://mywiki.wooledge.org/Quotes and http://wiki.bash-hackers.org/syntax/words
greybot sample from IRC freenode #bash is talking to the world =)
If it's a one-word constant, it's irrelevant.
However, you should read about the two kinds of quoting. Try this article and this documentation. There is also a SO question.
Try with a real example with whitespace. For the string example you do not need any quoting at all. So create a file called This is an example.txt and then retry. Substitute echo with ls...
Related
In a Linux shell, I want to print:
$300
$400
But when I do echo -e "$300\n$400" it shows:
00
00
When I do printf "$300\n$400" it shows the same thing!
So why does shell delete my dollar sign and the number right after it? Is there a way to print what I want?
You need to escape dollar $, since you are using double quotes, This will ensure the word is not interpreted by the shell.
$ echo -e "\$300\n\$400"
$300
$400
You may be aware how to access variables,
Example :
$ test="foo"
$ echo "$test"
foo
Suppose if you want to print $test, then you have use either
$ echo "\$test"
$test
OR with single quotes
$ echo '$test'
$test
In the shell, the $ character has a special meaning. It means "replace the $ and the following word or digit or special character with the value of a variable of that name". For example:
currency='EUR'
echo "The currency is $currency"
The variables 0, 1, 2, etc. contain the command line arguments to the program. So if you run your program as my-program Hello, world, you can write this code:
echo "argument 1 is $1"
echo "argument 2 is $2"
echo "both together are $1 $2, and all arguments are $*"
To make the $ character lose this special meaning, it must be written as \$. For example:
price=123
echo "The price is $price\$"
The first $ refers to the variable, and the second $ is escaped.
Alternatively you can surround your string in 'single quotes', which removes the special meaning of all characters.
To learn more about this topic, run the man bash command and read the section about variable expansion.
$ has special meaning to the shell; when it sees a $, it expects an existing shell variable name to follow. For example, $PATH.
In your case, you don't want the shell to think that you're trying to print out the value of shell variables, so you must tell the shell that the $ is indeed what you want to be displayed. This is done by preceding it with a backslash as explained in other answers.
Adding a backslash before characters is called escaping them (yes, not the most obvious terminology), and you are already using some escape characters unknowingly. (\n)
This applies to display other operators too, such as =, :, etc. Hope that helps.
You can use single quote. Enclosing characters in single-quotes (') shall preserve the literal value of each character within the single-quotes, where as enclosing characters in double-quotes(") shall preserve the literal value of all characters within the double-quotes, with the exception of the characters back quote, dollar-sign, and backslash.
echo -e '$'300"\n"'$'400
I have written a simple calculator in C using recursive-descent parser. It all works great but having a problem when I start with a '(' sign.
I take the user input from argv[1] and put the content in a global variable for temporary simplicity. Than I simply go through each character in the string and looking for what pattern it may be, ex number, add sign or multiplication (just like a regular recursive-descent parser work, no real fancy work).
But, if I do this:
./calculator (1+2)*0.5
I receive an error message,
bash: syntax error near unexpected token `1+2'
This is because I have to escape the '(' and ')' so
./calculator \(1+2\)*0.5
works fine.
So my questions is:
How do I solve this without having to think about eater putting single or double quotations around the equation or escape them?
Why is 0.5*(1+2) still working? Should I not have to escape the parenthesis there also?
What happens here is that your line looks like a function definition:
$ ./calculator () {
> echo "function called with arguments '$#'"
> }
defines a shell function called ./calculator. It can then be called like a command would be executed:
$ ./calculator arguments go here
function called with arguments 'arguments go here'
Your error stems from the fact that Bash expects that ( be followed by ) for it to be a proper function definition, but your parents weren't empty!
Bash shell reserves many meta characters on command line, not only parentheses. * is used for pathname generation. Different shells work differently; in Z shell (zsh), even this wouldn't work:
% ./calculator 1*2
zsh: no match
You should must escape all these metacharacters when given on command line. Do not learn a "safe subset" because soon you will try another shell and it fails. Or this might happen:
$ echo 1*2
1*2
$ touch 1-31337-2
$ echo 1*2
1-31337-2
There are two simple solutions to avoid backslashitis:
use single quotes around everything:
$ ./calculator '(1+2)*0.5'
works nicely if your string doesn't contain '.
Double quotes would also work but there are more meta characters that are reserved by bash within double quotes, for example $.
read the calculation from standard input instead, with a prompt
$ ./calculator
calculator> 1 + 2 * 0.5
you can use for example readline library for easy interactive editing, too!
Summary: Either quote your expressions (preferably with single quotes) or use something other than ( and ) for grouping.
For an answer to "why does 0.5*(1+2) work?", go to the end. (Hint: it's because you don't have a file named 0.5.)
Parentheses are what the bash manual refers to as metacharacters. (Posix no longer uses this term; instead, it refers to such characters as "operators". But the basic effect is the same.) Unless quoted, metacharacters are always tokens by themselves (or, in cases like << and &&, along with the rest of the operator they start), and they have syntactic significance.
This is different from braces ({ and }) which are reserved words, not metacharacters, and so do not delimit tokens. As reserved words, they only have special significance when they are tokens by themselves and are the first token in a command:
{echo x # The command to be executed is `{echo`, which probably doesn't exist
echo {x # No problem. Prints the string '{x'
echo { } # Also no problem. Prints '{ }'
{ echo x; } # A compound command. The ; is necessary.
(echo x) # Also a compound command but ; and whitespace are optional
[ and ] are somewhat similar. [ is a command (not even a reserved word), while [[ is a reserved word which starts a conditional compound command but only if it is the first word in a command.
So you could use brackets or braces as grouping operators without worrying about quoting, because the arguments to your function are never going to be the first words in the command.
As a side-note, the difference between [ (a command) and [[ (a reserved word) is shown by the fact that only the first one can be preceded by a variable assignment (in this case, the assignment has no useful effect):
$ foo=3 [ -z "$foo" ] && echo yes
yes
$ foo=3 [[ -z "$foo" ]] && echo yes
[[: command not found
$ [[ -z "$foo" ]] && echo yes
yes
The precise syntactic significance of parentheses depends, as usual, on the syntax in which they appear. In the case of (, this might be:
A function definition:
func () { echo "$#"; }
A compound command executed in a subshell
(sleep 1; echo "Hello..."; sleep 5; echo "World!")&
Surrounding the pattern in a case clause:
case "$word"; in
(Hello) echo "Hi" ;;
Bye) echo "Seeya" ;; # The open parenthesis is optional in this syntax
esac
In bash, it may also be used as part of array assignment:
local numbers=(one two three)
and it can form part of the (( operator, used in arithmetic conditional compound commands and arithmetic for statements.
Parentheses might also appear as part of a longer construct not starting with a parenthesis, such as command substitution: $(. But if a parenthesis is recognised as a token and it doesn't fit any of the syntactic constructs which include parentheses, a syntax error will be signalled:
$ echo a b(c)
bash: syntax error near unexpected token `('
That leaves us with a small mystery: how do we explain the following:
$ echo a+(b+4)
a+(b+4)
$ echo a-(b+4)
bash: syntax error near unexpected token `('
$ echo a*(b+4)
a*(b+4)
$ echo a/(b+4)
bash: syntax error near unexpected token `('
The answer is that I have shopt -s extglob in my bash start-up files. And you probably do, too, because many distributions do that for you by default. If "extended glob" patterns are available, then the following are patterns:
?(pattern-list)
Matches zero or one occurrence of the given patterns
*(pattern-list)
Matches zero or more occurrences of the given patterns
+(pattern-list)
Matches one or more occurrences of the given patterns
#(pattern-list)
Matches one of the given patterns
!(pattern-list)
Matches anything except one of the given patterns
A pattern-list can contain only a single pattern, so b+4 is a valid pattern, and a+(b+4) will therefore match a file whose name starts with an a and is followed by one or more instances of the characters b+4:
$ touch ab+4b+4b+4
$ echo a+(b+4)
ab+4b+4b+4
Like any other filename pattern, if no filename is matched, the pattern is not substituted:
$ rm ab+4b+4b+4
$ echo a+(b+4)
a+(b+4)
Unless you have other shell options set:
$ shopt -s failglob
$ echo a+(b+4)
bash: no match: a+(b+4)
I have a simple bash script to run a remote command on a given set of servers.
#!/bin/bash
echo "Command to be run:"
echo "$*"
read nothing
servers="server1 server2 server3"
for server in `echo $servers`
do
echo $server
ssh $server "$*"
echo ""
done
The problem is that the command could contain any number of arguments, hence the use of $* and could also have many different characters including quotes and regular expressions. The basic need here is for the shell to take the arguments, whatever they are, literally so they are passed to the remote server intact without removing quotes or interpreting parenthesis etc.
There are a number of variations I have seen but most deal with a specific character problem or overcomplicate the script or arguments required, and I'm looking to keep at least the arguments free of escape characters etc.
An example with using "#":
./cmd tw_query --no-headings "search Host where name matches '(?i)peter' show summary, nodecount(traverse :::Detail where name matches 'bob')"
Gives:
Command to be run:
tw_query --no-headings search Host where name matches '(?i)peter' show summary, nodecount(traverse :::Detail where name matches 'bob')
You seem to be looking for $#. Say:
ssh $server "$#"
instead. From the manual:
*
Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, it expands to a single word
with the value of each parameter separated by the first character of
the IFS special variable. That is, "$*" is equivalent to "$1c$2c…",
where c is the first character of the value of the IFS variable. If
IFS is unset, the parameters are separated by spaces. If IFS is null,
the parameters are joined without intervening separators.
#
Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, each parameter expands to a
separate word. That is, "$#" is equivalent to "$1" "$2" …. If the
double-quoted expansion occurs within a word, the expansion of the
first parameter is joined with the beginning part of the original
word, and the expansion of the last parameter is joined with the last
part of the original word. When there are no positional parameters,
"$#" and $# expand to nothing (i.e., they are removed).
You actually don't want the arguments passed to the remote server intact, you want them passed to the remote command intact. But that means they need to be wrapped in an extra layer of quotes/escapes/etc so that so that they will come out intact after the remote shell has parsed them.
bash actually has a feature in its printf builtin to add quoting/escaping to a string, but it quotes suitably for interpretation by bash itself -- if the remote shell were something else, it might not understand the quoting mode that it chooses. So in this case I'd recommend a simple-and-dumb quoting style: just add single-quotes around each argument, and replace each single-quote within the argument with '\'' (that'll end the current quoted string, add an escaped (literal) quote, then start another quoted string). It'll look a bit weird, but should decode properly under any POSIX-compliant shell.
Converting to this format is a bit tricky, since bash does inconsistent things with quotes in its search-and-replace patterns. Here's what I came up with:
#!/bin/bash
quotedquote="'\''"
printf -v quotedcommand "'%s' " "${#//\'/$quotedquote}"
echo "Command to be run:"
echo "$quotedcommand"
read nothing
servers="server1 server2 server3"
for server in $servers
do
echo $server
ssh $server "$quotedcommand"
echo ""
done
And here's how it quotes your example command:
'tw_query' '--no-headings' 'search Host where name matches '\''(?i)peter'\'' show summary, nodecount(traverse :::Detail where name matches '\''bob'\'')'
It looks strange to have the command itself quoted, but as long as you aren't trying to use an alias this doesn't cause any actual trouble. There is one significant limitation, though: there's no way to pass shell metacharacters (like > for output redirection) to the remote shell:
./cmd somecommand >outfile # redirect is done on local computer
./cmd somecommand '>outfile' # ">outfile" is passed to somecommand as an argument
If you need to do things like remote redirects, things get a good deal more complicated.
Besides the issue with $* versus $#, if this is for use in a production environment, you might want to consider a tool such as pdsh.
Otherwise, you can try feeding the commands to your script through stdin rather than putting them in argument so you avoid one level of parsing.
#!/bin/bash
read cmd
echo "Command to be run:"
echo $cmd
servers="server1 server2 server3"
for server in `echo $servers` do
echo $server
ssh $server "$cmd"
echo ""
done
and use it like this
$ ./cmd <<'EOT'
> tw_query --no-headings "search Host where name matches '(?i)peter' show summary, nodecount(traverse :::Detail where name matches 'bob')"
> EOT
Command to be run:
tw_query --no-headings "search Host where name matches '(?i)peter' show summary, nodecount(traverse :::Detail where name matches 'bob')"
Maybe a little far-fetched, but it could work.
Due to the default value of IFS, I am getting following result.
STR="this is a \"valid string\""
for a in $STR; do
echo $a;
done
will give output as:
this
is
a
"valid
string"
But I don't want to split "valid string".
I need output as:
this
is
a
"valid string"
So, how can I make shell ignore double quotes from IFS?
There are two types of quotes: Literal ones, and syntactic ones. The outer quotes of STR are syntactic, which means that they are not actually part of the string, they are just used to tell Bash where the string starts and ends. The inner (escaped) quotes are literal, so they are part of the string. In other words, the literal value of your string is:
this is a "valid string"
Now if you want to loop over this, you have to remember that this string consists of five (space-separated) literal words:
this
is
a
"valid
string"
If you want to get around this, one you can either use eval (but beware of the serious security issues) or arrays:
declare -a STR=(this is a "valid string")
for str in "${STR[#]}"
do
echo "$str"
done
That's not easily possible. You could switch to Perl instead. Or, if you have full control over the string, and nobody can possibly ever insert something evil into it, you could use this code:
str="a b c \"d e\""
eval "set args $str; shift"
for i in "$#"; do
echo "$i"
done
This will output the last word without the quotes, though. And it overwrites the command lune arguments to the shell program itself.
If you need to keep the quotes, this is called parsing and should not be done by a shell program, since it becomes too complicated.
VAR="-e xyz"
echo $VAR
This prints xyz, for some reason. I don't seem to be able to find a way to get a string to start with -e.
What is going on here?
The answers that say to put $VAR in quotes are only correct by side effect. That is, when put in quotes, echo(1) receives a single argument of -e xyz, and since that is not a valid option string, echo just prints it out. It is a side effect as echo could just as easily print an error regarding malformed options. Most programs will do this, but it seems GNU echo (from coreutils) and the version built into bash simply echo strings that start with a hyphen but are not valid argument strings. This behaviour is not documented so it should not be relied upon.
Further, if $VAR contains a valid echo option argument, then quoting $VAR will not help:
$ VAR="-e"
$ echo "$VAR"
$
Most GNU programs take -- as an argument to mean no more option processing — all the arguments after -- are to be processed as non-option arguments. bash echo does not support this so you cannot use it. Even if it did, it would not be portable. echo has other portability issues (-n vs \c, no -e).
The correct and portable solution is to use printf(1).
printf "%s\n" "$VAR"
The variable VAR contains -e xyz, if you access the variable via $ the -e is interpreted as a command-line option for echo. Note that the content of $VAR is not wrapped into "" automatically.
Use echo "$VAR" to fix your problem.
In zsh, you can use a single dash (-) before your arguments. This ensures that no following arguments are interpreted as options.
% VAR="-e xyz"
% echo - $VAR
-e xyz
From the zsh docs:
echo [ -neE ] [ arg ... ]
...
Note that for standards compliance a double dash does not
terminate option processing; instead, it is printed directly.
However, a single dash does terminate option processing, so the
first dash, possibly following options, is not printed, but
everything following it is printed as an argument.
The single dash behaviour is different from other shells.
Keep in mind this behavior is specific to zsh.
Try:
echo "$VAR"
instead.
(-e is a valid option for echo - this is what causes this phenomenon).