my bash script looks like:
#!/bin/sh
set -e
cat <<EOF > config.json
{
"host": "READER",
"server-url": "${server.url}",
"port": 8080,
}
EOF
when I run the file via ./generateJson.sh or bash generateJson.sh then I get the error
line 5: ..... : bad substitution
I have set the local variable server.url.
if I run echo $server.url in the same terminal session it has a string assigned.
what does the bad substitution error mean?
why is this happening?
I have seen many posts saying to use #!/bin/bash or other ways of making sure that it is running on bash not dash. But this makes no difference.
Variables in bash (or /bin/sh) can not contain dots in their names. This means that ${server.url} can not be expanded, and since it does not correspond to a know parameter substitution syntax, the shell complains with the error that you see.
Choose another name for your variable, e.g. server_url.
If your variable is actually called server and you want to add .url to its value, use $server.url in the here-document.
If you want to insert the literal string ${server.url} in the document, then either escape the $ as \$, or use 'EOF' or \EOF to quote the whole here-document (only at the start, not at the end).
Related
I have a simple bash script that does something like the following:
#!/bin/bash
a=$(curl -s http://metadata/endpoint/internal)
echo "$a - bar"
(this is just a simplification). Note the use of the two $ signs to execute a command and resolve a variable.
Using Terraform, I want to write this file to a GCP instance during startup. Per Terraform's instructions, I'm attempting to avoid using the File Provisioner. Using the metadata_startup_script field in google_compute_instance2, I am including the script so that I can write it to a particular location.
E.g.
metadata_startup_script = <<-EOF
#!/bin/bash -xe
sudo tee /etc/myservice/serv.conf > /dev/null <<EOI
${file("${path.module}/scripts/simple_bash.sh")}
EOI
EOF
Terraform is interpolating the $ in the subscript somewhere (either in the loading into the metadata_startup_script, or in the writing out into the script to disk.
So, depending on what I use to try to escape the interpolation, it still fails to write. For example, I have tried (in the subscript):
echo "\$a - bar"
echo "${ā$ā}a - bar"
echo "$$a - bar"
According to the terraform docs, I'm supposed to use $$, but when I do it in the above, I get:
echo "1397a - bar"
All which fail to replicate the original script.
Iām just looking for the exact bash script, as written, to be written to disk.
My goal would be to do the above without extra escape sequences (as detailed here - Escaping dollar sign in Terraform) so that i can continue to run the original script (for debugging purposes).
I would also prefer not to build a packer image with the original script in it.
Thanks!
I don't think it's Terraform eating your variable interpolations here, because Terraform only understands ${ (a dollar sign followed by a brace) as starting an interpolation, whereas your example contains only $a.
However, you do seem to be embedded one bash script inside another, so it seems plausible to me that the outer bash is resolving your $a before the inner bash gets a chance to look at it. If so, you can use the literal variant of bash heredoc syntax, as described in answers to How to cat <> a file containing code?, so that the outer bash will take the content as literal and leave it to the inner bash to evaluate.
metadata_startup_script = <<-EOF
#!/bin/bash -xe
sudo tee /etc/myservice/serv.conf > /dev/null <<'EOI'
${file("${path.module}/scripts/simple_bash.sh")}
EOI
EOF
Notice that I wrote <<'EOI' instead of <<EOI, following the guidance from that other question in combination with the main Bash documentation on "here documents" (bold emphasis mine):
This type of redirection instructs the shell to read input from the current source until a line containing only word (with no trailing blanks) is seen. All of the lines read up to that point are then used as the standard input (or file descriptor n if n is specified) for a command.
The format of here-documents is:
[n]<<[-]word
here-document
delimiter
No parameter and variable expansion, command substitution, arithmetic expansion, or filename expansion is performed on word. If any part of word is quoted, the delimiter is the result of quote removal on word, and the lines in the here-document are not expanded. If word is unquoted, all lines of the here-document are subjected to parameter expansion, command substitution, and arithmetic expansion, the character sequence \newline is ignored, and \ must be used to quote the characters \, $, and `.
If the redirection operator is <<-, then all leading tab characters are stripped from input lines and the line containing delimiter. This allows here-documents within shell scripts to be indented in a natural fashion.
If your machine image is configured to run cloud-init at startup -- this is often but not always what is responsible for executing metadata_startup_script -- you may be able to achieve a similar effect without so much Bash scripting indirection by using Cloud Config YAML instead of a shell script directly.
For example, if your intent is only to write the content of that file into the designated location in the filesystem, you could potentially follow the Writing out arbitrary files example:
metadata_startup_script = <<-EOF
#cloud-config
${yamlencode({
write_files = [
{
encoding = "b64"
content = filebase64("${path.module}/scripts/simple_bash.sh")
path = "/etc/myservice/serv.conf"
owner = "root:root"
permissions = "0644"
},
]
}}
EOF
Cloud-init evaluates its modules at various points in the startup lifecycle. The Write Files module used here is specified to run once on the first boot of an instance, which matches how Cloud-init would typically treat a naked shell script too.
I do not think your issue is related to TF interpolation. I think you have problems because of normal bash interpolation, as its bash which is going to try to resolve $ in your /etc/myservice/serv.conf while writing its content.
The regular solution is to use 'EOI', not EOI:
metadata_startup_script = <<-EOF
#!/bin/bash -xe
sudo tee /etc/myservice/serv.conf > /dev/null <<'EOI'
${file("${path.module}/scripts/simple_bash.sh")}
EOI
EOF
I have a script that outputs a list of env vars like:
THING=one
ANOTHER_THING=two
A_PATH="path/to a/directory"
When I try to export these env vars as export `./script`, the A_PATH env var exports as path/to.
If I export the list as plain text like:
export THING=one ANOTHER_THING=two A_PATH="path/to a/directory", it works just fine.
I'm stumped as to why bash treats the white space in the A_PATH differently in these two cases. I've tried various attempts at escaping the whitespace and I've even tried exporting line by line, but in every case it sees the whitespace as a delimiter rather than as a part of the path string.
why bash treats the white space in the A_PATH differently in these two cases
The result of command substitution `...` undergoes word splitting. Do not use ` backticks - use $(....) instead. Check your scripts with shellcheck.net .
Spaces inside string around double quotes are literally preserved. See quoting.
If the file has proper correct shell syntax, and it's meant to be sourced and support shell-ish execution, see https://unix.stackexchange.com/questions/614568/is-is-possible-to-export-all-variables-obtained-from-sourcing-a-file . If the file contains = separated variable name and optionally quoted string with custom syntax, write a parser for the file for that syntax - see ex. Parsing variables from config file in Bash for a stub to get started.
Without the actual code, it is difficult to understand what is going on. But one workaround that might solve your problem is to output the export command and evaluate it.
Example (script.sh):
#!/bin/bash
echo "export THING=one"
echo "export ANOTHER_THING=two"
echo "export A_PATH='path/to a/directory'"
eval $(./script.sh)
This seems like it should be simple, but I'm pulling out my remaining hair trying to get it to work. In a shell script I want to run some Applescript code that defines a string, then pass that string (containing a single quote) to a shell command that calls PHP's addslashes function, to return a string with that single quote escaped properly.
Here's the code I have so far - it's returning a syntax error.
STRING=$(osascript -- - <<'EOF'
set s to "It's me"
return "['test'=>'" & (do shell script "php -r 'echo addslashes(\"" & s & "\");") & "']"
EOF)
echo -e $STRING
It's supposed to return this:
['test'=>'It\'s me']
First, when asking a question like this, please include what's happening, not just what you're trying to do. When I try this, I get:
42:99: execution error: sh: -c: line 0: unexpected EOF while looking for matchin
sh: -c: line 1: syntax error: unexpected end of file (2)
(which is actually two error messages, with one partly overwriting the other.) Is that what you're getting?
If it is, the problem is that the inner shell command you're creating has quoting issues. Take a look at the AppleScript snippet that tries to run a shell command:
do shell script "php -r 'echo addslashes(\"" & s & "\");"
Since s is set to It's me, this runs the shell command:
php -r 'echo addslashes("It's me");
Which has the problem that the apostrophe in It's me is acting as a close-quote for the string that starts 'echo .... After that, the double-quote in me"); is seen as opening a new quoted string, which doesn't get closed before the end of the "file", causing the unexpected EOF problem.
The underlying problem is that you're trying to pass a string from AppleScript to shell to php... but each of those has its own rules for parsing strings (with different ideas about how quoting and escaping work). Worse, it looks like you're doing this so you can get an escaped string (following which set of escaping rules?) to pass to something else... This way lies madness.
I'm not sure what the real goal is here, but there has to be a better way; something that doesn't involve a game of telephone with players that all speak different languages. If not, you're pretty much doomed.
BTW, there are a few other dubious shell-scripting practices in the script:
Don't use all-caps variable named in shell scripts. There are a bunch of all-caps variables that have special meanings, and if you accidentally use one of those for something else, weird results can happen.
Put double-quotes around all variable references in scripts, to avoid them getting split into multiple "words" and/or expanded as shell wildcards. For example, if the variable string was set to "['test'=>'It\'s-me']", and you happened to have files named "t" and "m" in the current directory, echo -e $string will print "m t" because those are the files that match the [] pattern.
Don't use echo with options and/or to print strings that might contain escapes, since different versions treat these things differently. Some versions, for example, will print the "-e" as part of the output string. Use printf instead. The first argument to printf is a format string that tells it how to format all of the rest of the arguments. To emulate echo -e "$string" in a more reliable form, use printf '%b\n' "$string".
To complement Gordon Davisson's helpful answer with a pragmatic solution:
Shell strings cannot contain \0 (NUL) characters, but the following sed command emulates all other escaping that PHP's (oddly named) addslashes PHP function performs (\-escaping ', " and \ instances):
string=$(osascript <<'EOF'
set s to "It's me\\you and we got 3\" of rain."
return "['test'=>'" & (do shell script "sed 's/[\"\\\\'\\'']/\\\\&/g' <<<" & quoted form of s) & "']"
EOF
)
printf '%s\n' "$string"
yields
['test'=>'It\'s me\\you and we got 3\" of rain.']
Note the use of quoted form of, which is crucial for passing a string from AppleScript to a do shell script shell command with proper quoting.
Also note how the closing here-doc delimiter, EOF, is on its own line to ensure that it is properly recognized (in Bash 3.2.57, as used on macOS 10.12, (also when called as /bin/sh, which is what do shell script does), this isn't strictly necessary, but Bash 4.x would rightfully complain about EOF) with warning: here-document at line <n> delimited by end-of-file (wanted 'EOF')
I'm writing a shell script with user input like so:
echo -n "Enter string for Read Group parameter: "
read readgroup
I am passing this variable to another application that requires this parameter to be a string that starts with # and is enclosed in quotes on the command line.
This is the correct command-line syntax:
application -R '#text'
application -R "#text"
These commands would produce errors if run on command line:
application -R #text
application -R "text"
application -R 'text'
In my script, I have tried putting escaped quotes in the passed variable:
echo $readgroup
'#text'
application -R "$readgroup" # error: does not start with #
Or passing it without the quotes:
echo $readgroup
#text
application -R "$readgroup" # error: no parameter entry recognized
application -R \""$readgroup"\" # error: does not start with #
application -R "'"'$readgroup'"'" # error: does not start with #
And tried other solutions from stackoverflow like arrays:
readgroup=("#text")
application -R "${readgroup[#]}" # error: does not start with #
The problem is that when I include the quotes in the original variable or escape the quotes on the command call, the application interprets it as a literal string (e.g. beginning with a quote and therefore invalid, since it must begin with an #). However when I don't include the quotes, it returns an error because there are no quotes for the application call.
Is there a way to force the shell to interpret the escaped quotes (in the script) as actual command line quotes (parameters) or to add those quotes explicitly in the application call in the script?
Or is a different approach considered better, such as using arguments for the script instead?
Thanks.
Application environment is not specified here, can you elaborate more about your application and its environment.
Also did you tried application -R "#$readgroup"
I think eval "application -R '$readgroup'" would do literally the same as you wrote in correct command line sytax, since it gets the whole command as a string.
On the other hand there might be something tricky going on. I think inside application it can't be decided if $readgroup was specified with or without quotes. At last the main() will only get unquoted char *argv[]!
I want to write a script that will change to different directories depending on my input. something like this:
test.sh:
#!/bin/bash
ssh machine001 '(chdir ~/dev$1; pwd)'
But as I run ./test.sh 2 it still goes to ~/dev. It seems that my argument gets ignored. Am I doing anything very stupid here?
Bash ignores any variable syntax inside the single-quoted(') strings. You need double quotes(") in order to make a substitution:
#!/bin/bash
ssh machine001 "(chdir ~/dev$1; pwd)"
The parameter is enclosed in single quotes, so it isn't expanded on the local side. Use double-quotes instead.
#!/bin/bash
ssh machine001 "chdir ~/dev$1; pwd"
There's no need for the (...), since you are only running the pair of commands then exiting.