Bash variable character replacement ends up to an empty string or a command not valid - macos

I am working on a shell script to retrieve variable content from a JSON file via JQ. The JSON file is in string format (no matter whether this is a real string or a number) and to retrieve the variable in my bash script I did something like this
my_domain=$(cat /vagrant/data_bags/config.json | jq ."app"[0]."domain")
The above code once echoed results in "mydomain" with a beginning and a trailing quote sign. I though this was a normal behaviour of the echo command. However, while concatenating my variable with another shell command the system raise an error. For instance, the following command
cp /vagrant/public_html/index.php "/var/www/"+$my_domain+"/index.php"
fails with the following error
cp: cannot create regular file `/var/www/+"mydomain"+/index.php': No such file or directory
At this stage, I wasn't able to identify whether it's me doing the wrong concatenation with the plus sign or the variable is effectively including the quotes that in any case will end up generating an error.
I have tried to replace the quotes in my variable, but I ended up getting the system raising a "Command not found" error.
Can somebody suggest what am I doing wrong?

+ is not used for string concatenation in bash (or perl, or php). Just:
cp /vagrant/public_html/index.php "/var/www/$my_domain/index.php"
Embedding a variable inside a double-quoted text string is known as interpolation, and is one of the reasons why we need the $ prefix, to indicate that this is a variable. Interpolation is specifically not done inside single quoted strings.
Braces ${my_domain} are not required because the / directory separators are not valid characters in a variable name, so there is no ambiguity.
For example:
var='thing'
echo "Give me your ${var}s" # Correct, appends an 's' after 'thing'
echo "Give me your $vars" # incorrect, looks for a variable called vars.
If a variable (like 'vars') does not exist then (by default) it will not complain, it will just give an empty string. Braces (graph brackets) are required more in c-shell (csh or tcsh) because of additional syntax for modifying variables, which involves special trailing characters.

You don't need to use + to concatenate string in bash, change your command to
cp /vagrant/public_html/index.php "/var/www/"${my_domain}"/index.php"

My problem was not related only to the wrong concatenation, but also to the JQ library that after parsing the value from the JSon file was returning text between quotes.
In order to avoid JQ doing this, just add the -rawoutput parameter when calling JQ.

Related

Can I pass a json object as value for a cli flag in go?

I am using urfave/cli for my go program and I would like to have a cli flag that reads a json value like this one:
{"name":"foo","surname":"var"}
I am currently reading that variable as a cli.StringFlag which returns a string. Then, I was planning to json.Unmarshall it but it does not work. The problem is that the returned string by the cli library is like this:
[{name foo} {surname var}]
which is not a json anymore.
Is there a way to achieve this? Note that if it returned a simple map, that would work too
for Linux, try to pass the paramaters with shell escape
#!/bin/bash
echo "{\"name\":\"foo\",\"surname\":\"var\"}"
in go program, just marshal this string parameter
The issue is that the shell (bash, ksh, csh, zsh, ...) interprets
{"name":"foo","surname":"var"}
as a sequence of bareword and quoted word tokens:
Token Type
Value
bareword
{
quoted word
name
bareword
:
quoted word
foo
bareword
,
quoted word
surname
bareword
:
quoted word
var
bare word
}
As it happens, a comma (,) is a shell operator, used for arithmetic, and that essentially gets discarded (at least in zsh, what I use).
The whole is then spliced together to get
name:foo surname:var
You can see this in action by opening your shell and executing the command
echo {"name":"foo","surname":"var"}
If, however, you quote your JSON document with single quotes ('):
echo '{"name":"foo","surname":"var"}'
You'll get what you might expect:
{"name":"foo","surname":"var"}
Note, however, that this will fail if the text in your JSON document contains a literal apostrophe/single quote (', U+0027), so you'd want to replace all such occurrences within the JSON document with \, to escape them.

Braces in shell parameter expansion don't work right

I have a program parsing two files and comparing them looking for conflicts between the two and allowing the user to decide what action to take. As a result, I need to be able to parse the lines below. If a string contains { or } when using pattern replacement parameter expansion it will cause an error.
I was looking for a potential work around for the following lines
F=TSM_CLASS="Test text {class}"
newstring=${F//{class}/\\{class\\}}
Results:
echo $newstring
TSM_CLASS="Test text }/\{class\}}"
${F//{class} is a complete parameter expansion which replaces every instance of {class in F's value with empty string. To embed braces in the pattern and/or the replacement string, you need to quote them.
$ F=TSM_CLASS="Test text {class}"
$
$ echo "${F//{class\}/\\{class\\\}}"
TSM_CLASS=Test text \{class\}

Unix bash assigning string with spaces to a variable

I am facing issue while assigning sting with spaces to variable. Below is my command.
test.ksh should accept "remove file" as one variable. But I am unable to do so.
hello="/export/appl/<userid>/test.ksh remove file"
$hello
As #Jetchisel mentionned, you should quote your string that contains spaces otherwise it will be interpreted as separated value ($1=remove and $2=file ). Moreover by looking what you want to achieve, I would suggest to use command substitution:
hello=$(/export/appl//test.ksh "remove file")

Bash - Why does $VAR1=FOO or 'VAR=FOO' (with quotes) return command not found?

For each of two examples below I'll try to explain what result I expected and what I got instead. I'm hoping for you to help me understand why I was wrong.
1)
VAR1=VAR2
$VAR1=FOO
result: -bash: VAR2=FOO: command not found
In the second line, $VAR1 gets expanded to VAR2, but why does Bash interpret the resulting VAR2=FOO as a command name rather than a variable assignment?
2)
'VAR=FOO'
result: -bash: VAR=FOO: command not found
Why do the quotes make Bash treat the variable assignment as a command name?
Could you please describe, step by step, how Bash processes my two examples?
How best to indirectly assign variables is adequately answered in other Q&A entries in this knowledgebase. Among those:
Indirect variable assignment in bash
Saving function output into a variable named in an argument
If that's what you actually intend to ask, then this question should be closed as a duplicate. I'm going to make a contrary assumption and focus on the literal question -- why your other approaches failed -- below.
What does the POSIX sh language specify as a valid assignment? Why does $var1=foo or 'var=foo' fail?
Background: On the POSIX sh specification
The POSIX shell command language specification is very specific about what constitutes an assignment, as quoted below:
4.21 Variable Assignment
In the shell command language, a word consisting of the following parts:
varname=value
When used in a context where assignment is defined to occur and at no other time, the value (representing a word or field) shall be assigned as the value of the variable denoted by varname.
The varname and value parts shall meet the requirements for a name and a word, respectively, except that they are delimited by the embedded unquoted equals-sign, in addition to other delimiters.
Also, from section 2.9.1, on Simple Commands, with emphasis added:
The words that are recognized as variable assignments or redirections according to Shell Grammar Rules are saved for processing in steps 3 and 4.
The words that are not variable assignments or redirections shall be expanded. If any fields remain following their expansion, the first field shall be considered the command name and remaining fields are the arguments for the command.
Redirections shall be performed as described in Redirection.
Each variable assignment shall be expanded for tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal prior to assigning the value.
Also, from the grammar:
If all the characters preceding '=' form a valid name (see the Base Definitions volume of IEEE Std 1003.1-2001, Section 3.230, Name), the token ASSIGNMENT_WORD shall be returned. (Quoted characters cannot participate in forming a valid name.)
Note from this:
The command must be recognized as an assignment at the very beginning of the parsing sequence, before any expansions (or quote removal!) have taken place.
The name must be a valid name. Literal quotes are not part of a valid variable name.
The equals sign must be unquoted. In your second example, the entire string was quoted.
Assignments are recognized before tilde expansion, parameter expansion, command substitution, etc.
Why $var1=foo fails to act as an assignment
As given in the grammar, all characters before the = in an assignment must be valid characters within a variable name for an assignment to be recognized. $ is not a valid character in a name. Because assignments are recognized in step 1 of simple command processing, before expansion takes place, the literal text $var1, not the value of that variable, is used for this matching.
Why 'var=foo' fails to act as an assignment
First, all characters before the = must be valid in variable names, and ' is not valid in a variable name.
Second, an assignment is only recognized if the = is not quoted.
1)
VAR1=VAR2
$VAR1=FOO
You want to use a variable name contained in a variable for the assignment. Bash syntax does not allow this. However, there is an easy workaround :
VAR1=VAR2
declare "$VAR1"=FOO
It works with local and export too.
2)
By using single quotes (double quotes would yield the same result), you are telling Bash that what is inside is a string and to treat it as a single entity. Since it is the first item on the line, Bash tries to find an alias, or shell builtin, or an executable file in its PATH, that would be named VAR=FOO. Not finding it, it tells you there is no such command.
An assignment is not a normal command. To perform an assignment contained in a quote, you would need to use eval, like so :
eval "$VAR1=FOO" # But please don't do that in real life
Most experienced bash programmers would probably tell you to avoid eval, as it has serious drawbacks, and I am giving it as an example just to recommend against its use : while in the example above it would not involve any security risk or error potential because the value of VAR1 is known and safe, there are many cases where an arbitrary (i.e. user-supplied) value could cause a crash or unexpected behavior. Quoting inside an eval statement is also more difficult and reduces readability.
You declare VAR2 earlier in the program, right?
If you are trying to assign the value of VAR2 to VAR1, then you need to make sure and use $ in front of VAR2, like so:
VAR1=$VAR2
That will set the value of VAR2 equal to VAR1, because when you utilize the $, you are saying that value that is stored in the variable. Otherwise it doesn't recognize it as a variable.
Basically, a variable that doesn't have a $ in front of it will be interpreted as a command. Any word will. That's why we have the $ to clarify "hey this is a variable".

BASH function for escaping spaces in filenames before opening them

I've been trying to write a function for my bash profile for quite some time now.
The problem I'm trying to overcome is I'm usually provided with file paths that include spaces and it's a pain having to go through and escape all the spaces before I try to open it up in terminal.
e.g.
File -> /Volumes/Company/Illustrators/Website Front Page Design.ai
What I'm trying to end up with is '/Volumes/Company/Illustrators/Website\ Front\ Page\ Design.ai' being opened from my terminal.
So far I've managed to escape the spaces out, but I then get the error "The file ..... does not exist."
My code so far is
function opn { open "${1// /\\ }";}
Any help would be very much appreciated.
The important thing to understand is the difference between syntax and literal data.
When done correctly, escaping is syntax: It's read and discarded by the shell. That is, when you run
open "File With Spaces"
or
open File\ With\ Spaces
or even
open File" "With\ Spaces
...the quoting and escaping is parsed and removed by the shell, and the actual operating system call that gets executed is this:
execv("/usr/bin/open", "open", "File With Spaces")
Note that there aren't any backslashes (or literal quotes) in that syscall's arguments! If you put literal backslashes in your data, then you cause this to be run:
/* this is C syntax, so "\\" is a single-character backslash literal */
execv("/usr/bin/open", "open", "File\\ With\\ Spaces")
...and unless there's a file with backslashes in its name, that just doesn't work, giving the "file does not exist" error you report.
So -- just call open with your name in quotes:
open "$1"
...there's no need for an opn wrappper.
Spaces are problematic in filenames because they're part of bash's default IFS (Internal Field Separator), which is used to separate tokens in a command line. That means that by default, when you use command an argument with spaces, the command will receive 4 arguments rather than 1 containing spaces.
I'm guessing you called your opn function in the same way, thus resulting in only the first part of your path as $1.
Hopefully, the fix is easy : enclose your path in quotes so that bash does not interpret the spaces. By using this, the need for your opn function disappears : open "/Volumes/Company/Illustrators/Website Front Page Design.ai" should work just fine.

Resources