Bash grep is not working with apostrophe - bash

I've tried to run this code:
lsof | grep "/file/path/"
If the file has this sign:
`
I get an error:
syntax error near unexpected token '`'
I've tried to escape this sign with \, but I got the same error.
Operation System: CentOS 6

The ` character (often called a "backquote") is interpreted specially by the shell, even when surrounded by quotes.
Specifically, `cmd args` means "execute the command cmd args and replace the backquoted string with the output from that command." If it is surrounded by quotes -- "`cmd args`" -- the replaced string will not undergo word-splitting or pathname-expansion. (That's usually what you want, so quoted backquotes are common.)
That syntax has been deprecated for a long time, but it is still accepted. New code should use $(cmd args) instead. As above, you usually want to avoid word-splitting and pathname-expanding the replaced text, so you'll normally see "$(cmd args)".
In short, if you want to put a literal backquote into an argument string, you should either use single quotes:
lsof | grep '/file/path`s/'
or \-escape the backquote:
lsof | grep "/file/path\`s/"

Related

Trouble understanding the non-obvious use of backslash inside of backticks

I have read a ton of pages including the bash manual, but still find the "non-obvious" use of backslashes confusing.
If I do:
echo \*
it prints a single asterisks, this is normal as I am escaping the asterisks making it literal.
If I do:
echo \\*
it prints \*
This also seems normal, the first backslash escapes the second.
If I do
echo `echo \\*`
It prints the contents of the directory. But in my mind it should print the same as echo \\* because when that is substituted and passed to echo. I understand this is the non-obvious use of backslashes everyone talks about, but I am struggling to understand WHY it happens.
Also the bash manual says
When the old-style backquote form of substitution is used, backslash retains its literal meaning except when followed by ‘$’, ‘`’, or ‘\’.
But it doesn't define what the "literal meaning on backslash" is. Is it as an escape character, a continuation character, or just literally a backslash character?
Also, it says it retain it's literal meaning, except when followed by ... So when it's followed by one of those three characters what does it do? Does it only escape those three characters?
This is mostly for historical interest since `...` command substitution has been superseded by the cleaner $(...) form. No new script should ever use backticks.
Here's how you evaluate a $(command) substitution
Run the command
Here's how you evaluate a `string` command substitution:
Determine the span of the string, from the opening backtick to the closing unescaped backtick (behavior is undefined if this backtick is inside a string literal: the shell will typically either treat it as literal backtick or as a closing backtick depending on its parser implementation)
Unescape the string by removing backslashes that come before one of the three characters dollar, backtick or backslash. This following character is then inserted literally into the command. A backslash followed by any other character will be left alone.
E.g. Hello\\ World will become Hello\ World, because the \\ is replaced with \
Hello\ World will also become Hello\ World, because the backslash is followed by a character other than one of those three, and therefore retains its literal meaning of just being a backslash
\\\* will become \\* since the \\ will become just \ (since backslash is one of the three), and the \* will remain \* (since asterisk is not)
Evaluate the result as a shell command (this includes following all regular shell escaping rules on the result of the now-unescaped command string)
So to evaluate echo `echo \\*`:
Determine the span of the string, here echo \\*
Unescape it according to the backtick quoting rules: echo \*
Evaluate it as a command, which runs echo to output a literal *
Since the result of the substitution is unquoted, the output will undergo:
Word splitting: * becomes * (since it's just one word)
Pathname expansion on each of the words, so * becomes bin Desktop Downloads Photos public_html according to files in the current directory
Note in particular that this was not the same as replacing the the backtick command with the output and rerunning the result. For example, we did not consider escapes, quotes and expansions in the output, which a simple text based macro expansion would have.
Pass each of these as arguments to the next command (also echo): echo bin Desktop Downloads Photos public_html
The result is a list of files in the current directory.

Sed issue parenthesis escape

I try to substitute a line in my .sql file with the sed command.
I encountered an issue when i try to "escape" the last parenthesis of the line.
I tried to escape it with a backlash , used simple and double quotes around it but the problem persist.
sed -i "s|\(select dbms_metadata.get_ddl('PACKAGE',\).*|\1"'${var}','PRC_BNE_JZ') from dual;"|" backup_script.sql
The actual result is the following error :
syntax error near unexpected token `)'
The expected result is to understand my error.
Untested since you didn't provide any sample input/output to test against but this might be what you're looking for:
sed -i 's/\(select dbms_metadata.get_ddl('\''PACKAGE'\'',\).*/\1'"${var}"','\''PRC_BNE_JZ'\'') from dual;/' backup_script.sql
Don't use | as a delimiter since it's an ERE metacharacter and so makes your code confusing to read at best and can cause problems if/when you decide to use EREs later (via the -E arg).
Do always use single quotes around scripts and strings unless you have a very specific reason why you must not do so and fully understand all the implications.
I do everything in single quotes, to prevent strange expansions. To have a single quote in the string, I use '\'' as in ' string '\'' rest of string '. To have a variable I use '"$var"' as in ' string '"$var"' rest of string to have it properly expanded and concatenated with the rest of the string.
The following works:
> var=var
> echo "select dbms_metadata.get_ddl('PACKAGE'," |
> sed 's|\(select dbms_metadata.get_ddl('\''PACKAGE'\'',\).*|\1'\'"${var}"\'','\''PRC_BNE_JZ'\'') from dual;|'
select dbms_metadata.get_ddl('PACKAGE','var','PRC_BNE_JZ') from dual;
But probably using " is probably easier in this case, as the string uses ' everywhere, as in:
sed "s|\(select dbms_metadata.get_ddl('PACKAGE',\).*|\1'${var}','PRC_BNE_JZ') from dual;|"
The error comes from the shell that can't parse the arguments. Ex. for the following:
> echo abc)
main.sh: line 2: syntax error near unexpected token `)'
main.sh: line 2: `echo abc)'
The following happens in your command:
sed -i " bla bla "'${var}','PRC_BNE_JZ') from dual;"|" backup_script.sql
^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^ - could be 4th argument
^ - unquoted `)` is parsed by bash, as in subshell `( ... )`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - 2nd argument
^^ - 1st argument to sed command
^^^ - run sed command

Escaping a literal asterisk as part of a command

Sample bash script
QRY="select * from mysql"
CMD="mysql -e \"$QRY\""
`$CMD`
I get errors because the * is getting evaluated as a glob (enumerating) files in my CWD.
I Have seen other posts that talk about quoting the "$CMD" reference for purposes of echo output, but in this case
"$CMD"
complains the whole literal string as a command.
If I
echo "$CMD"
And then copy/paste it to the command line, things seems to work.
You can just use:
qry='select * from db'
mysql -e "$qry"
This will not subject to * expansion by shell.
If you want to store mysql command line also then use BASH arrays:
cmd=(mysql -e "$qry")
"${cmd[#]}"
Note: anubhava's answer has the right solution.
This answer provides background information.
As for why your approach didn't work:
"$CMD" doesn't work, because bash sees the entire value as a single token that it interprets as a command name, which obviously fails.
`$CMD`
i.e., enclosing $CMD in backticks, is pointless in this case (and will have unintended side effects if the command produces stdout output[1]); using just:
$CMD
yields the same - broken - result (only more efficiently - by enclosing in backticks, you needlessly create a subshell; use backticks - or, better, $(...) only when embedding one command in another - see command substitution).
$CMD doesn't work,
because unquoted use of * subjects it to pathname expansion (globbing) - among other shell expansions.
\-escaping glob chars. in the string causes the \ to be preserved when the string is executed.
While it may seem that you've enclosed the * in double quotes by placing it (indirectly) between escaped double quotes (\"$QRY\") inside a double-quoted string, the shell does not see what's between these escaped double quotes as a single, double-quoted string.
Instead, these double quotes become literal parts of the tokens they abut, and the shell still performs word splitting (parsing into separate arguments by whitespace) on the string, and expansions such as globbing on the resulting tokens.
If we assume for a moment that globbing is turned off (via set -f), here is the breakdown of the arguments passed to mysql when the shell evaluates (unquoted) $CMD:
-e # $1 - all remaining arguments are the unintentionally split SQL command.
"select # $2 - note that " has become a literal part of the argument
* # $3
from # $4
mysql" # $5 - note that " has become a literal part of the argument
The only way to get your solution to work with the existing, single string variable is to use eval as follows:
eval "$CMD"
That way, the embedded escaped double-quoted string is properly parsed as a single, double-quoted string (to which no globbing is applied), which (after quote removal) is passed as a single argument to mysql.
However, eval is generally to be avoided due to its security implications (if you don't (fully) control the string's content, arbitrary commands could be executed).
Again, refer to anubhava's answer for the proper solution.
[1] A note re using `$CMD` as a command by itself:
It causes bash to execute stdout output from $CMD as another command, which is rarely the intent, and will typically result in a broken command or, worse, a command with unintended effects.
Try running `echo ha` (with the backticks - same as: $(echo ha)); you'll get -bash: ha: command not found, because bash tries to execute the command's output - ha - as a command, which fails.

In bash, why don't I need to escape nested quotes?

I've got a variable named formatted_deploy_history that has multi-lined content. I'm trying to pipe that content into a series of commands and save the end result to a variable. Here's what I've done:
nwhin_version="$(echo "${formatted_deploy_history}" | grep "productName" | grep "NHINC" | head -n1 | grep -o "[0-9]*")"
This works the way I want, but it seems like it should be a syntax error. Why is it that the second quote doesn't interfere with the rest of the command? I'd expect a syntax error because this would be interpreted like this:
nwhin_version="$(echo "
with trailing characters.
Your string contains command substitution ($(...)), which is its own world - delimited by $( and ).
Inside that world, you're free to use double quotes without escaping.
See section 2.3, "Token Recognition", in the POSIX Shell Command Language specification:
If the current character is an unquoted $ or `, the shell shall identify the start of any candidates for parameter expansion ( Parameter Expansion), command substitution ( Command Substitution), or arithmetic expansion ( Arithmetic Expansion) from their introductory unquoted character sequences: $ or ${, $( or ```, and "$((", respectively. The shell shall read sufficient input to determine the end of the unit to be expanded (as explained in the cited sections). While processing the characters, if instances of expansions or quoting are found nested within the substitution, the shell shall recursively process them in the manner specified for the construct that is found. The characters found from the beginning of the substitution to its end, allowing for any recursion necessary to recognize embedded constructs, shall be included unmodified in the result token, including any embedded or enclosing substitution operators or quotes. The token shall not be delimited by the end of the substitution.
I believe you don't need quotes around the command substitution.
Try this:
nwhin_version=$(echo "${formatted_deploy_history}" | grep "productName" | grep "NHINC" | head -n1 | grep -o "[0-9]*")

Backticks vs braces in Bash

When I went to answer this question, I was going to use the ${} notation, as I've seen so many times on here that it's preferable to backticks.
However, when I tried
joulesFinal=${echo $joules2 \* $cpu | bc}
I got the message
-bash: ${echo $joules * $cpu | bc}: bad substitution
but
joulesFinal=`echo $joules2 \* $cpu | bc`
works fine. So what other changes do I need to make?
The `` is called Command Substitution and is equivalent to $() (parenthesis), while you are using ${} (curly braces).
So all of these expressions are equal and mean "interpret the command placed inside":
joulesFinal=`echo $joules2 \* $cpu | bc`
joulesFinal=$(echo $joules2 \* $cpu | bc)
# v v
# ( instead of { v
# ) instead of }
While ${} expressions are used for variable substitution.
Note, though, that backticks are deprecated, while $() is POSIX compatible, so you should prefer the latter.
From man bash:
Command substitution allows the output of a command to replace the
command name. There are two forms:
$(command)
or
`command`
Also, `` are more difficult to handle, you cannot nest them for example. See comments below and also Why is $(...) preferred over ... (backticks)?.
They behave slightly differently in a specific case:
$ echo "`echo \"test\" `"
test
$ echo "$(echo \"test\" )"
"test"
So backticks silently remove the double quotes.
${} refer to Shell parameter expansion. Manual link:https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
The ‘$’ character introduces parameter expansion, command
substitution, or arithmetic expansion. The parameter name or symbol to
be expanded may be enclosed in braces, which are optional but serve to
protect the variable to be expanded from characters immediately
following it which could be interpreted as part of the name.
When braces are used, the matching ending brace is the first ‘}’ not
escaped by a backslash or within a quoted string, and not within an
embedded arithmetic expansion, command substitution, or parameter
expansion.
FULLPATH=/usr/share/X11/test.conf_d/sk-synaptics.conf
echo ${FULLPATH##*/}
echo ${FILENAME##*.}
First echo will get filename. second will get file extension as per manual ${parameter##word} section.
$(command)
`command`
refer to command substitution.
Bash performs the expansion by executing command in a subshell
environment and replacing the command substitution with the standard
output of the command, with any trailing newlines deleted.
https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html

Resources