This question already has answers here:
I just assigned a variable, but echo $variable shows something else
(7 answers)
Closed last year.
The following outputs the directory display for the tmp directory.
Why, and how to stop it.
#!/bin/bash
cd /tmp
echo '*' >zzz
cat zzz
IFS='' read something <zzz
echo ${something}
Quote your expansions.
echo "${something}"
See Parameter Expansion, Word Splitting and Filename Expansion.
The shell scans the results of parameter expansion, command
substitution, and arithmetic expansion that did not occur within
double quotes for word splitting.
Related
This question already has answers here:
What is the difference between $* and $#
(4 answers)
How do I pass on script arguments that contain quotes/spaces?
(2 answers)
Closed last month.
I want a bash script, call it args, to execute a command that is the arguments to the script.
In particular, I would like this command (note multiple blanks):
$./args echo 'foobar *0x0'
to execute this precise command:
echo 'foobar *0x0'
I tried this in args:
#!/bin/bash
set -x
$*
but it doesn't work:
./args echo 'foobar *0x0'
+ echo foobar '*0x0'
foobar *0x0
Witness the single space, as well as moved single quotes.
With $#, the result is exactly the same, so please don't close the question on the account of differences between $* and $#. Also, blanks are not my only problem, there is the *0x0.
#!/bin/bash
"$#"
This expands to all of the command-line arguments with spacing and quoting intact. $*, by contrast, is subject to unwanted word splitting and globbing since it's not quoted.
This question already has answers here:
Why isn't tilde (~) expanding inside double quotes? [duplicate]
(1 answer)
Tilde expansion in quotes
(3 answers)
Tilde in path doesn't expand to home directory
(5 answers)
Closed 4 years ago.
I have made a directory ~/test_myDir
I then run the following bash script:
x="myDir"
dirName="~/test_$x"
cd $dirName
echo "hey" > test.txt
I get the following error:
test.sh: line 5: cd: ~/test_myDir: No such file or directory
I then remove the quotes from the second assignment:
x="myDir"
dirName=~/test_$x
cd $dirName
echo "hey" > test.txt
The script runs without error.
What is going on here? I ran into this issue in a larger, more complicated script, and I narrowed it down to my use of quotes in a variable assignment that contained another variable.
Still, from the error message, it looks like the full path is being expanded correctly in the "cd" call.
Quotation marks prevent expansion of ~. Replace ~ with $HOME or use dirName=~/"test_$x".
From the manual's explanation of tilde expansion:
Each variable assignment is checked for unquoted tilde-prefixes immediately following a : or the first =. In these cases, tilde expansion is
also performed.
This question already has answers here:
"~/Desktop/test.txt: No such file or directory"
(2 answers)
Closed 4 years ago.
I have a very surprising problem while trying to execute a diff command inside a bash script.
Here is a working code illustrating the point:
#!/bin/bash
cd
mkdir foo bar
head -c 1024 /dev/urandom >foo/qux
head -c 1024 /dev/urandom >bar/qux
# works properly as expected
diff ~/{foo,bar}/qux
folder="~"
# this fails with the ~ inside a variable
diff $folder/{foo,bar}/qux
# cleaning the mess
rm -rf foo bar
So my question is:
Why ? :-)
Don't quote the ~ when assigning it to a variable. The ~ is only expanded by bash when you don't quote it.
~ is a feature of shell expansion.
Double quotes limit the expansion to only three features:
Command substitution: $(some command here) or `some command here`
Variable substitution: $VAR or ${VAR}
Arithmetic: $((2+2))
so when put inside double quotes, the ~ is not expanded
Tilde expansion only applies to unquoted tildes. The tilde must be expanded at the time you perform the assignment to folder, because tilde expansion is not applied to parameter expansions, only word splitting and pathname expansion.
folder=~ # ~ is expanded, and the result is assigned to folder
This question already has answers here:
Why does shell ignore quoting characters in arguments passed to it through variables? [duplicate]
(3 answers)
Closed 3 years ago.
Explaining the question through examples...
Demonstrates that the single-quotes after --chapters is gets escaped when the variable is expanded (I didn't expect this):
prompt#ubuntu:/my/scripts$ cat test1.sh
#!/bin/bash
actions="--tags all:"
actions+=" --chapters ''"
mkvpropedit "$1" $actions
prompt#ubuntu:/my/scripts$ ./test1.sh some.mkv
Error: Could not open '''' for reading.
And now for some reason mkvpropedit receives the double quotes as part of the filename (I didn't expect this either):
prompt#ubuntu:/my/scripts$ cat test1x.sh
#!/bin/bash
command="mkvpropedit \"$1\""
command+=" --tags all:"
command+=" --chapters ''"
echo "$command"
$command
prompt#ubuntu:/my/scripts$ ./test1x.sh some.mkv
mkvpropedit "some.mkv" --tags all: --chapters ''
Error: Could not open '''' for reading.
The above echo'd command seems to be correct. Putting the same text in another script gives the expected result:
prompt#ubuntu:/my/scripts$ cat test2.sh
#!/bin/bash
mkvpropedit "$1" --tags all: --chapters ''
prompt#ubuntu:/my/scripts$ ./test2.sh some.mkv
The file is being analyzed.
The changes are written to the file.
Done.
Could anyone please explain why the quotes are not behaving as expected. I found searching on this issue difficult as there are so many other quoting discussions on the web. I wouldn't even know how to explain the question without examples.
I am afraid that some day the file name in the argument contains some character that breaks everything, hence the maybe excessive quoting. I do not understand why the same command executes differently when typed directly in the script or when provided via a variable. Please enlighten me.
Thanks for reading.
The important thing to keep in mind is that quotes are only removed once, when the command line is originally parsed. A quote which is inserted into the command line as a result of parameter substitution ($foo) or command substitution ($(cmd args)) is not treated as a special character. [Note 1]
That seems different from whitespace and glob metacharacters. Word splitting and pathname expansion happen after parameter/command substitution (unless the substitution occurs inside quotes). [Note 2]
The consequence is that it is almost impossible to create a bash variable $args such that
cmd $args
If $args contains quotes, they are not removed. Words inside $args are delimited by sequences of whitespace, not single whitespace characters.
The only way to do it is to set $IFS to include some non-whitespace character; that character can then be used inside $args as a single-character delimiter. However, there is no way to quote a character inside a value, so once you do that, the character you chose cannot be used other than as a delimiter. This is not usually very satisfactory.
There is a solution, though: bash arrays.
If you make $args into an array variable, then you can expand it with the repeated-quote syntax:
cmd "${args[#]}"
which produces exactly one word per element of $args, and suppresses word-splitting and pathname expansion on those words, so they end up as literals.
So, for example:
actions=(--tags all:)
actions+=(--chapters '')
mkvpropedit "$1" "${actions[#]}"
will probably do what you want. So would:
args=("$1")
args+=(--tags)
args+=(all:)
args+=(--chapters)
args+=('')
mkvpropedit "${args[#]}"
and so would
command=(mkvpropedit "$1" --tags all: --chapters '')
"${command[#]}"
I hope that's semi-clear.
man bash (or the online version) contains a blow-by-blow account of how bash assembles commands, starting at the section "EXPANSION". It's worth reading for a full explanation.
Notes:
This doesn't apply to eval or commands like bash -c which evaluate their argument again after command line processing. But that's because command-line processing happens twice.
Word splitting is not the same as "dividing the command into words", which happens when the command is parsed. For one thing, word-splitting uses as separator characters the value of $IFS, whereas command-line parsing uses whitespace. But neither of these are done inside quotes, so they are similar in that respect. In any case, words are split in one way or another both before and after parameter substitution.
This question already has answers here:
Strange behavior of argv when passing string containing "!!!!"
(3 answers)
Closed 5 years ago.
Why does this command line work:
$ output='Irrelevant'; if [[ $output =~ Something ]]; then echo "I found something in the output." ; fi
And this one give me a strange parse error?
$ output='Irrelevant'; if [[ $output =~ Something ]]; then echo "I found something in the output!" ; fi
-bash: !": event not found
The only change from the first version is that the sentence to be echoed inside quotes ends with an exclamation mark. Why does Bash give me that error in the second version?
In case it matters, this is the output from bash --version:
GNU bash, version 4.2.24(1)-release (x86_64-pc-linux-gnu)
You can wrap the string in single quotes instead of double quotes.
The exclamation point invokes the very useful history expansion function described in the bash manual.
History expansions are introduced by the appearance of the history expansion character, which is ! by default. Only \ and ' may be used to escape the history expansion character.
For instance, to execute the last command that started with the word mysql type this:
!mysql
or to execute the last command containing the word grep, type this:
!?grep
The bash manual also documents the syntax of the history expansion operators.