Theory: who can explain the use of = - bash

can someone explain me with this code
data=$(date +"%Y-%m-%dS%H:%M:%S")
name="/home/cft/"$data"_test.tar"
touch $name
works, creating a new .tar file but this code doesn't work
data=$(date +"%Y-%m-%dS%H:%M:%S")
name= "/home/cft/"$data"_test.tar"
touch $name
and gives me this error: no such file or directory?
why the space between = and inverted commas creates this error?

Shell allows you to provide per-command environment overrides by prefixing the command with one or more variable assignments.
name= "/home/cft/"$data"_test.tar"
asks the shell to run the program named /home/cft/2013-10-08S12:00:00_test.tar (for example) with the value of name set to the empty string in its environment.
(In your case, the error occurs because the named tar file either doesn't exist or, if it does, is not an executable file.)
A variable assignment is identified by having no whitespace after the equal sign.
(name = whatever, of course, is simply a command called name with two string arguments, = and whatever.)

You can't have whitespace between the equal sign and the definition.
http://www.tldp.org/LDP/abs/html/varassignment.html
There is no theory behind this. It's just a decision the language designers made, and which the parser enforces.

In BASH (and other Bourne type shells like zsh and Kornshell), the equal sign cannot have spaces around it when setting variables.
Good:
$ foo="bar"
Bad:
$ foo= "bar"
$ foo = "bar"
There's no real reason that would prevent spaces from being used. Other programming languages have no problems with this. It's just the syntax of the shell itself.
The reason might be related to the original Bourne shell parsing where the shell would break up a command line based upon whitespace. That would make foo=bar a single parameter instead of two or three (depending if you have white space on both sides or just one side of the equal sign). The shell could see the = sign, and know this parameter is an assignment.
The shell parameter parsing is very primitive in many ways. Whitespace is very important. The shell has to be small and fast in order to be responsive. That means stripping down unessential things like complex line parsing.
Inverted commas I believe you mean quotation marks. Double quotes are used to override the breaking out of parameters over white space:
Bad:
$ foo=this is a test
bash: is: command not found
Good:
$ foo="this is a test"
Double quotes allow interpolation. Single quotes don't:
$ foo="bar"
$ echo "The value of foo is $foo"
The value of foo is bar
$ echo 'The value of foo is $foo'
The value of foo is $foo.
If you start out with single quotes, you can put double quotes inside. If you have single quotes, you can put double quotes inside.
$ foo="bar"
$ echo "The value of foo is '$foo'"
The value of foo is 'bar'
$ echo 'The value of foo is "$foo"'
The value of foo is "$foo"
This means you didn't have to unquote $data. However, you would have to put curly braces around it because underscores are legal characters in variable names. Thus, you want to make sure that the shell understand that the variable is $data and not $data_backup:
name="/home/cft/${data}_test.tar"

Related

Best practice for defining variables in BASH configuration files

I read in http://wiki.bash-hackers.org/howto/conffile that it is recommended to put double quotes around values in BASH configuration files which are to be sourced into a script.
The file to be sourced should be formated in key="value" format,
otherwise bash will try to interpret commands
However, I am not aware of any difference in BASH's behavior when sourcing a configuration file if the value has double quotes or not, assuming there is no whitespace in the value. I'm sure there are some more complex cases where the double quotes are vital (e.g. using other variables as the value), but for the simple cases below, would double quotes cause BASH to behave any differently, even if the difference is only behind-the-scenes? I'm wondering if the first configuration file below could cause BASH to search for a named foobar before assigning it as a string, but from my testing it doesn't appear to do so.
# Configuration file 1
myDir=/var/tmp/test/
myString=foobar
myInteger=20
# Configuration file 2
myDir="/var/tmp/test/"
myString="foobar"
myInteger="20"
source configurationFile1
echo "$myDir"
echo "$myString"
echo "$myInteger"
source configurationFile2
echo "$myDir"
echo "$myString"
echo "$myInteger"
It's a style issue. In the examples you show, the quotes aren't strictly necessary. myDir=/var/tmp/text and myDir="/var/tmp/text" do exactly the same thing. Other values may require quotes to make the assignment correct.
The allusion is to the fact that these aren't really configuration files; they're just bash scripts that are intended to contain only assignments. Something like
foo=bar baz
is not an assignment; it's a simple command that tries to run baz with a variable named foo in its environment. Here, quotes are required:
foo="bar baz"
to make a proper assignment, in contrast to other "actual" configuration file formats where everything following the = (and optionally some post-= whitespace`) is considered part of the value to be assigned.

I am trying to define a string X="either 'this|that'" but GNU make won't accept it

I am trying to write a Makefile for GNU make. I can't figure out what the problem is here:
foo := this|works
bar := "I lost my 'single quotes'"
baz := 'make|will|not|accept|this|with|the|single|quotes'
whatIWant := "A string with its 'single|quoted|regex|alternatives'"
this-almost-works: #But the single quotes are lost.
#printf '%s ' "$(whatIWant)"
this-fails-horribly:
#printf '$(whatIWant)'
I get the following error message
/bin/sh: 1: quoted: not found
/bin/sh: 1: /bin/sh: 1: regex: not foundalternatives": not found
blah blah Error 127
Why is it trying to run parts of this string in the shell?
How can I define a variable to contain exactly the contents of whatIWant?
Might be worth looking in detail at the expansion.
When defining variables,
just about the only character that has an effect is $.
Everything else is taken literally.
It's worth nothing that white space around the assignment operator (= or :=) is ignored.
foo := this|works
foo is assigned the literal text this|works.
Similarly,
baz := 'make|will|not|accept|this|with|the|single|quotes'
assigns the literal text 'make|will|not|accept|this|with|the|single|quotes' to baz.
Fine and dandy.
Now, when make decides to build this-fails-horribly
(possibly because you said to the shell make this-fails-horribly)
it expands the block of commands before doing anything.
Not unreasonably,
$(whatIWant) is replaced by "A string with its 'single|quoted|regex|alternatives'".
Again, fine and dandy.
What is left is passed verbatim, one line at a time, to the shell.
The shell sees
printf '"A string with its 'single|quoted|regex|alternatives'"'
(which make would have helpfully echoed to you if you had left off the # prefix).
Now we are in the land of shell quoting.
The printf command is passed one parameter: "A string with its single:
'"A string with its ' is a single quoted string. The shell strips the 's and is left with the text "A string with its.
single has no metacharacters in it, so the shell leaves this alone.
The output is piped to the quoted command
The output is piped to the regex command
The output is piped to the alternatives" command
The shell sees the single quoted string '=', strips the quotes leaving you with a literal = which it appends to the word alternatives
No syntax error.
When the shell attempts to set up the pipeline it looks for the alternatives" command.
It doesn't find one in the directories it its $PATH, so it stops with the message /bin/sh: 1: /bin/sh: 1: regex: not foundalternatives": not found.
One possible encoding:
.PHONY: this-workes-nicely
this-workes-nicely:
echo $(whatIWant)
though you'll probably find it's cleaner to leave the quotes outside the variable definition in the first place.

Echoing an environment variable, keeping newlines intact? [duplicate]

This question already has answers here:
When to wrap quotes around a shell variable?
(5 answers)
Closed 7 years ago.
I want to create some scripts for filling some templates and inserting them into my project folder. I want to use a shell script for this, and the templates are very small so I want to embed them in the shell script. The problem is that echo seems to ignore the line breaks in my string. Either that, or the string doesn't contain line breaks to begin with. Here is an example:
MY_STRING="
Hello, world! This
Is
A
Multi lined
String."
echo -e $MY_STRING
This outputs:
Hello, world! This Is A Multi lined String.
I'm assuming echo is the culprit here. How can I get it to acknowledge the line breaks?
You need double quotes around the variable interpolation.
echo -e "$MY_STRING"
This is an all-too common error. You should get into the habit of always quoting strings, unless you specifically need to split into whitespace-separated tokens or have wildcards expanded.
So to be explicit, the shell will normalize whitespace when it parses your command line. You can see this if you write a simple C program which prints out its argv array.
argv[0]='Hello,'
argv[1]='world!'
argv[2]='This'
argv[3]='Is'
argv[4]='A'
argv[5]='Multi'
argv[6]='lined'
argv[7]='String.'
By contrast, with quoting, the whole string is in argv[0], newlines and all.
For what it's worth, also consider here documents (with cat, not echo):
cat <<"HERE"
foo
Bar
HERE
You can also interpolate a variable in a here document.
cat <<HERE
$MY_STRING
HERE
... although in this particular case, it's hardly what you want.
echo is so nineties. The new (POSIX) kid on the block is printf.
printf '%s\n' "$MY_STRING"
No -e or SYSV vs BSD echo madness and full control over what gets printed where and how wide, escape sequences like in C. Everybody please start using printf now and never look back.
Try this :
echo "$MY_STRING"

why doesn't bash IFS value split expansion argument?

>export FOOBAR=foobar; IFS=b echo ${FOOBAR}
I was expecting to see
foo ar
but I see
foobar
Why?
The IFS hasnt yet taken effect. add another ";":
FOOBAR=foobar IFS=b; echo ${FOOBAR}
In man bash section SIMPLE COMMAND EXPANSION
you can read (abbreviated):
When a simple command is executed
The words that the parser has marked as variable assignments (those preceding the command name) are saved for later processing.
The words that are not variable assignments or redirections are expanded.
...
The text after the = in each variable assignment ... [are] assigned to the variable.
so the IFS=b is done after expanding $FOOBAR.
[edit]I removed the technically incorrect answer.
http://tldp.org/LDP/abs/html/internalvariables.html
"This variable determines how Bash recognizes fields, or word boundaries, when it interprets character strings."

how to escape paths to be executed with $( )?

I have program whose textual output I want to directly execute in a shell. How shall I format the output of this program such that the paths with spaces are accepted by the shell ?
$(echo ls /folderA/folder\ with\ spaces/)
Some more info: the program that generates the output is coded in Haskell (source). It's a simple program that keeps a list of my favorite commands. It prints the commands with 'cmdl -l'. I can then choose one command to execute with 'cmdl -g12' for command number 12. Thanks for pointing out that instead of $( ) use 'cmdl -g12 | bash', I wasn't aware of that...
How shall I format the output of this program such that the paths with
spaces are accepted by the shell ?
The shell cannot distinguish between spaces that are part of a path and spaces that are separator between arguments, unless those are properly quoted. Moreover, you actually need proper quoting using single quotes ('...') in order to "shield" all those characters combinations that might otherwise have special meaning for the shell (\, &, |, ||, ...).
Depending the language used for your external tool, their might be a library available for that purpose. As as example, Python has pipes.quote (shlex.quote on Python 3) and Perl has String::ShellQuote::shell_quote.
I'm not quite sure I understand, but don't you just want to pipe through the shell?
For a program called foo
$ foo | sh
To format output from your program so Bash won't try to space-separate them into arguments either update, probably easiest just to double-quote them with any normal quoting method around each argument, e.g.
mkdir "/tmp/Joey \"The Lips\" Fagan"
As you saw, you can backslash the spaces alternatively, but I find that less readable ususally.
EDIT:
If you may have special shell characters (&|``()[]$ etc), you'll have to do it the hard/proper way (with a specific escaper for your language and target - as others have mentioned.
It's not just spaces you need to worry about, but other characters such as [ and ] (glob a.k.a pathname-expansion characters) and metacharacters such as ;, &, (, ...
You can use the following approach:
Enclose the string in single quotes.
Replace existing single quotes in the string with '\'' (which effectively breaks the string into multiple parts with spliced in \-escaped single quotes; the shell then reassembles the parts into a single string).
Example:
I'm good (& well[1];) would encode to 'I'\''m good (& well[1]);'
Note how single-quoting allows literal use of the glob characters and metacharacters.
Since single quotes themselves can never be used within single-quoted strings (there's not even an escape), the splicing-in approach described above is needed.
As described by #mklement0, a safe algorithm is to wrap every argument in a pair of single quotes, and quote single quotes inside arguments as '\''. Here is a shell function that does it:
function quote {
typeset cmd="" escaped
for arg; do
escaped=${arg//\'/\'\\\'\'}
cmd="$cmd '$escaped'"
done
printf %s "$cmd"
}
$ quote foo "bar baz" "don't do it"
'foo' 'bar baz' 'don'\''t do it'

Resources