Print variable containing command - linux

Can somebody tell me how to print variable and not executing it in bash? I mean:
bash#bash $ cat script.sh
#!/bin/bash
echo $1
bash#bash $ ./script.sh "`date`"
Sat Sep 20 18:42:19 CEST 2014
I don't want to get:
Sat Sep 20 18:35:37 CEST 2014
I want to get output:
date
I'm interested in how to prevent executing sent commands to script.

It looks like you are trying to prevent code from being injected into your script. The problem with echo $1 is that the contents of $1 are being evaluated by the shell. In order to avoid that, you need to wrap $1 in double quotes in your script:
#!/bin/bash
echo "$1"
Testing it out:
$ ./script.sh '`date`'
`date`
The problem in your question is that you are using double quotes around "date", so the expansion has occurred before your script is run. You can use set -x to see the difference:
$ set -x
$ ./script '`date`'
+ ./script '`date`'
`date`
$ ./script "`date`"
++ date # date is being run
+ ./script 'Sat Sep 20 18:01:32 BST 2014' # result is passed to your script
Sat Sep 20 18:01:32 BST 2014
There is nothing you can do about this.
I think the following section of my original answer is still relevant, so I'll leave it in.
Different types of quotes in bash
Backticks (which you have used in your question) are an old-fashioned way of capturing the output of executing a command. The more modern syntax is $( ), so if you wanted to store the current date in a variable you could do d=$(date). Single quotes are used for literal strings, so echo '$d' would output $d, not the value of the variable. Variables inside double quotes are expanded, so echo "$d" would output the value of the variable $d. It is always a good idea to wrap your variables in double quotes to prevent word splitting and glob expansion.

Replace the backticks from var1 with single quotes:
var1='date'
var2="echo $var1"
echo $var2

Related

shell script with xargs and command line argument [duplicate]

I'm trying to write a bash script that allows the user to pass a directory path using wildcards.
For example,
bash show_files.sh *
when executed within this directory
drw-r--r-- 2 root root 4.0K Sep 18 11:33 dir_a
-rw-r--r-- 1 root root 223 Sep 18 11:33 file_b.txt
-rw-rw-r-- 1 root root 106 Oct 18 15:48 file_c.sql
would output:
dir_a
file_b.txt
file_c.sql
The way it is right now, it outputs:
dir_a
contents of show_files.sh:
#!/bin/bash
dirs="$1"
for dir in $dirs
do
echo $dir
done
The parent shell, the one invoking bash show_files.sh *, expands the * for you.
In your script, you need to use:
for dir in "$#"
do
echo "$dir"
done
The double quotes ensure that multiple spaces etc in file names are handled correctly.
See also How to iterate over arguments in a bash shell script.
Potentially confusing addendum
If you're truly sure you want to get the script to expand the *, you have to make sure that * is passed to the script (enclosed in quotes, as in the other answers), and then make sure it is expanded at the right point in the processing (which is not trivial). At that point, I'd use an array.
names=( $# )
for file in "${names[#]}"
do
echo "$file"
done
I don't often use $# without the double quotes, but this is one time when it is more or less the correct thing to do. The tricky part is that it won't handle wild cards with spaces in very well.
Consider:
$ > "double space.c"
$ > "double space.h"
$ echo double\ \ space.?
double space.c double space.h
$
That works fine. But try passing that as a wild-card to the script and ... well, let's just say it gets to be tricky at that point.
If you want to extract $2 separately, then you can use:
names=( $1 )
for file in "${names[#]}"
do
echo "$file"
done
# ... use $2 ...
Quote the wild-card:
bash show_files.sh '*'
or make your script accept a list of arguments, not just one:
for dir in "$#"
do
echo "$dir"
done
It's better to iterate directly over "$#' rather than assigning it to another variable, in order to preserve its special ability to hold elements that themselves contain whitespace.

Better way to pick a random entry from args?

Was just wondering because I whipped this up last month.
#!/usr/bin/bash
# Collects all of the args, make sure to seperate with ','
IN="$*"
# Takes everything before a ',' and places them each on a single line of tmp file
echo $IN | sed 's/,/\n/g' > /tmp/pick.a.random.word.or.phrase
# Obvious vars are obvious
WORDFILE="/tmp/pick.a.random.word.or.phrase"
# Pick only one of the vars
NUMWORDS=1
## Picks a random line from tmp file
#Number of lines in $WORDFILE
tL=`awk 'NF!=0 {++c} END {print c}' $WORDFILE`
# Expand random
RANDOM_CMD='od -vAn -N4 -tu4 /dev/urandom'
for i in `seq $NUMWORDS`
do
rnum=$((`${RANDOM_CMD}`%$tL+1))
sed -n "$rnum p" $WORDFILE | tr '\n' ' '
done
printf "\n"
rm /tmp/pick.a.random.word.or.phrase
Mainly I ask:
Do I need to have a tmp file?
Is there a way to do this in one line with another program?
How to condense as much as possible?
The command-line argument handling is, to my mind, bizarre. Why not just use normal command line arguments? That makes the problem trivial:
#!/usr/bin/bash
shuf -en1 "$#"
Of course, you could just use shuf -en1, which is only nine keystrokes:
$ shuf -en1 word another_word "random phrase"
another_word
$ shuf -en1 word another_word "random phrase"
word
$ shuf -en1 word another_word "random phrase"
another_word
$ shuf -en1 word another_word "random phrase"
random phrase
shuf command-line flags:
-e Shuffle command line arguments instead of lines in a file/stdin
-n1 Produce only the first random line (or argument in this case)
If you really insist on running the arguments together and then separating them with commas, you can use the following. As with your original, it will exhibit unexpected behaviour if some word in the arguments could be glob-expanded, so I really don't recommend it:
#!/usr/bin/bash
IFS=, read -ra args <<<"$*"
echo $(shuf -en1 "${args[#]}")
The first line combines the arguments and then splits the result at commas into the array args. (The -a option to read.) Since the string is split at commas, spaces (such as though automatically inserted by the argument concatenation) are preserved; to remove the spaces, I word-split the result of shuf by not quoting the command expansion.
You could use shuff to shorten your script and remove temporary file.
#!/usr/bin/bash
# Collects all of the args, make sure to seperate with ','
IN="$*"
# Takes everything before a ',' and places them in an array
words=($(echo $IN | sed 's/,/ /g'))
# Get random indexi in range: 0, length of array: words
index=$(shuf -i 0-"${#words[#]}" -n 1)
# Print the random index
echo ${words[$index]}
If you don't want to use shuff, you could also use $RANDOM:
#!/usr/bin/bash
# Collects all of the args, make sure to seperate with ','
IN="$*"
# Takes everything before a ',' and places them in an array
words=($(echo $IN | sed 's/,/ /g'))
# Print the random index
echo ${words[$RANDOM % ${#words[#]}]}
shuf in coreutils does exactly this, but with multiple command arguments instead of a single comma separated argument.
shuf -n1 -e arg1 arg2 ...
The -n1 option says to choose just one element. The -e option indicates that elements will be passed as arguments (as opposed to through standard input).
Your script then just needs to replace commas with spaces in $*. We can do this using bash parameter substitution:
#!/usr/bin/bash
shuf -n1 -e ${*//,/ }
This won't work with elements with embedded spaces.
Isn't it as simple as generating a number at random between 1 and $# and simply echo the corresponding argument? It depends on what you have; your comment about 'collect arguments; make sure to separate with commas' isn't clear, because the assignment does nothing with commas — and you don't show how you invoke your command.
I've simply cribbed the random number generation from the question: it works OK on my Mac, generating the values 42,405,691 and 1,817,261,076 on successive runs.
n=$(( $(od -vAn -N4 -tu4 /dev/urandom) % $# + 1 ))
eval echo "\${$n}"
You could even reduce that to a single line if you were really determined:
eval echo "\${$(( $(od -vAn -N4 -tu4 /dev/urandom) % $# + 1 ))}"
This use of eval is safe as it involves no user input. The script should check that it is provided at least one argument to prevent a division-by-zero error if $# is 0. The code does an absolute minimum of data movement — in contrast to solutions which shuffle the data in some way.
If that's packaged in a script random_selection, then I can run:
$ bash random_selection Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
Feb
$ bash random_selection Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
Oct
$ bash random_selection Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
Nov
$
If the total number of arguments is big enough that you run out of argument space, then you need to think again, but that restriction is present in the existing code.
The selection is marginally biassed towards the earlier entries in the list; you have to do a better job of rejecting random numbers that are very near the maximum value in the range. For a random 32-bit unsigned value, if it is larger than $# * (0xFFFFFFFF / $#) you should generate another random number.

Bash - Date command and space

I am trying to create a script that uses the date command in bash. I am familiar with the basic syntax of the date command.
Here is the simple script:
#!/bin/bash
set -x
DATE_COMMAND="date "+%y-%m-%d %H:%M:%S""
echo "$($DATE_COMMAND)"
set +x
The thing is that the above code doesn't work.
Here is the output:
+ DATE_COMMAND='date +%y-%m-%d'
+ %H:%M:%S
onlyDate: line 3: fg: no job control
+ echo ''
+ set +x
Ok, so the problem is that the bash splits the command because of the space. I can understand that but I don't know how to avoid that. I have tried to avoid the space with \, to avoid the space and the ". Also the single quotes doesn't seem to work.
Please note that I know that this script can be written this way:
#!/bin/bash
set -x
DATE_COMMAND=$(date "+%y-%m-%d %H:%M:%S")
echo "$DATE_COMMAND"
set +x
I have tried that but I can't use this approach because I want to run the command several times in my script.
Any help will be really appreciated!
The correct approach is to define your own function inside your Bash script.
function my_date {
date "+%y-%m-%d %H:%M:%S"
}
Now you can use my_date as if it were an external program.
For example:
echo "It is now $(my_date)."
Or simply:
my_date
Why isn't your approach working?
The first problem is that your assignment is broken.
DATE_COMMAND="date "+%y-%m-%d %H:%M:%S""
This is parsed as an assignment of the string date +%y-%m-%d to the variable DATE_COMMAND. After the blank, the shell starts interpreting the remaining symbols in ways you did not intend.
This could be partially fixed by changing the quotation.
DATE_COMMAND="date '+%y-%m-%d %H:%M:%S'"
However, this doesn't really solve the problem because if we now use
echo $($DATE_COMMAND)
It will not expand the argument correctly. The date program will see the arguments '+%y-%m-%d and %H:%M:%S' (with quotes) instead of a single string. This could be solved by using eval as in
DATE_COMMAND="date '+%y-%m-%d %H:%M:%S'"
echo $(eval $DATE_COMMAND)
where the variable DATE_COMMAND is first expanded to the string date '+%y-%m-%d %H:%M:%S' that is then evaluated as if it were written like so thus invoking date correctly.
Note that I'm only showing this to explain the issue. eval is not a good solution here. Use a function instead.
PS It is better to avoid all-uppercase identifier strings as those are often in conflict with environment variables or even have a magic meaning to the shell.
Escaping the space works for me.
echo `date +%d.%m.%Y\ %R.%S.%3N`
For long scripts
declare variables section:
dateformat="%Y-%m-%d %H:%M:%S"
anywhere to get date:
datestr=`date +"${dateformat}"`
anywhere to echo date:
echo ${datestr}
For short simple scripts DaniëlGu's answer is the best:
echo `date +%d.%m.%Y\ %R.%S.%3N`
Shortest answer is
#if you want to store in a variable
now=$(date '+%F" "%T');
echo $now
#or direct output
date '+%F" "%T'
I'm using:
DF="+%m/%d/%Y %H:%M:%S";
FILE_DATE=$(date "${DF[#]}" -r "${SCHEMA_LOCAL_PATH}.gz");
Seems Bash treats space in $DF as an array's separator and 'date' receives 2 agruments instead of one so I'm sending an array.
Also works for me (check "+" sign):
DF="%m/%d/%Y %H:%M:%S";
FILE_DATE=$(date +"${DF}" -r "${SCHEMA_LOCAL_PATH}.gz");

How to cat <<EOF >> a file containing code?

I want to print code into a file using cat <<EOF >>:
cat <<EOF >> brightup.sh
!/bin/bash
curr=`cat /sys/class/backlight/intel_backlight/actual_brightness`
if [ $curr -lt 4477 ]; then
curr=$((curr+406));
echo $curr > /sys/class/backlight/intel_backlight/brightness;
fi
EOF
but when I check the file output, I get this:
!/bin/bash
curr=1634
if [ -lt 4477 ]; then
curr=406;
echo > /sys/class/backlight/intel_backlight/brightness;
fi
I tried putting single quotes but the output also carries the single quotes with it. How can I avoid this issue?
You only need a minimal change; single-quote the here-document delimiter after <<.
cat <<'EOF' >> brightup.sh
or equivalently backslash-escape it:
cat <<\EOF >>brightup.sh
Without quoting, the here document will undergo variable substitution, backticks will be evaluated, etc, like you discovered.
If you need to expand some, but not all, values, you need to individually escape the ones you want to prevent.
cat <<EOF >>brightup.sh
#!/bin/sh
# Created on $(date # : <<-- this will be evaluated before cat;)
echo "\$HOME will not be evaluated because it is backslash-escaped"
EOF
will produce
#!/bin/sh
# Created on Fri Feb 16 11:00:18 UTC 2018
echo "$HOME will not be evaluated because it is backslash-escaped"
As suggested by #fedorqui, here is the relevant section from man bash:
Here Documents
This type of redirection instructs the shell to read input from the
current source until a line containing only delimiter (with no
trailing blanks) is seen. All of the lines read up to that point are
then used as the standard input for a command.
The format of here-documents is:
<<[-]word
here-document
delimiter
No parameter expansion, command substitution, arithmetic expansion,
or pathname expansion is performed on word. If any characters in word
are 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. In the
latter case, the character sequence \<newline> is ignored, and \
must be used to quote the characters \, $, and `.
This should work, I just tested it out and it worked as expected: no expansion, substitution, or what-have-you took place.
cat <<< '
#!/bin/bash
curr=`cat /sys/class/backlight/intel_backlight/actual_brightness`
if [ $curr -lt 4477 ]; then
curr=$((curr+406));
echo $curr > /sys/class/backlight/intel_backlight/brightness;
fi' > file # use overwrite mode so that you don't keep on appending the same script to that file over and over again, unless that's what you want.
Using the following also works.
cat <<< ' > file
... code ...'
Also, it's worth noting that when using heredocs, such as << EOF, substitution and variable expansion and the like takes place. So doing something like this:
cat << EOF > file
cd "$HOME"
echo "$PWD" # echo the current path
EOF
will always result in the expansion of the variables $HOME and $PWD. So if your home directory is /home/foobar and the current path is /home/foobar/bin, file will look like this:
cd "/home/foobar"
echo "/home/foobar/bin"
instead of the expected:
cd "$HOME"
echo "$PWD"
Or, using your EOF markers, you need to quote the initial marker so expansion won't be done:
#-----v---v------
cat <<'EOF' >> brightup.sh
#!/bin/bash
curr=`cat /sys/class/backlight/intel_backlight/actual_brightness`
if [ $curr -lt 4477 ]; then
curr=$((curr+406));
echo $curr > /sys/class/backlight/intel_backlight/brightness;
fi
EOF
IHTH
I know this is a two year old question, but this is a quick answer for those searching for a 'how to'.
If you don't want to have to put quotes around anything you can simply write a block of text to a file, and escape variables you want to export as text (for instance for use in a script) and not escape one's you want to export as the value of the variable.
#!/bin/bash
FILE_NAME="test.txt"
VAR_EXAMPLE="\"string\""
cat > ${FILE_NAME} << EOF
\${VAR_EXAMPLE}=${VAR_EXAMPLE} in ${FILE_NAME}
EOF
Will write '"${VAR_EXAMPLE}="string" in test.txt' into test.txt
This can also be used to output blocks of text to the console with the same rules by omitting the file name
#!/bin/bash
VAR_EXAMPLE="\"string\""
cat << EOF
\${VAR_EXAMPLE}=${VAR_EXAMPLE} to console
EOF
Will output '"${VAR_EXAMPLE}="string" to console'
cat with <<EOF>> will create or append the content to the existing file, won't overwrite. whereas cat with <<EOF> will create or overwrite the content.
cat test.txt
hello
cat <<EOF>> test.txt
> hi
> EOF
cat test.txt
hello
hi
cat <<EOF> test.txt
> haiiiii
> EOF
cat test.txt
haiiiii

Escape special characters in echo

http://www.grymoire.com/Unix/Quote.html shows a list of special characters. Is there a parameter/option for echo where I can treat everything that comes after the echo as a string?
In python, i could use the """...""" or '''...'''.
$ python
>>> text = '''#"`\|^!###%$#$^%$&%^*()?/\;:$#$#$?$$$!&&'''
>>> print text
#"`\|^!###%$#$^%$&%^*()?/\;:$#$#$?$$$!&&
I can do the same in unix's echo with ''' but not """, why is that so?
$ echo #"`\|^!###%$#$^%$&%^*()?/\;:$#$#$?$$$!&&
$ echo '''#"`\|^!###%$#$^%$&%^*()?/\;:$#$#$?$$$!&&'''
#"`\|^!###%$#$^%$&%^*()?/\;:$#$#$?$$$!&&
$ echo """'''#"`\|^!###%$#$^%$&%^*()?/\;:$#$#$?$$$!&&"""
bash: !###%$#$^%$: event not found
What happens if i have a string like this?
#"`\|^!###%$#$^%'''$&%^*()?/\;:$#$"""#$?$$$!&&
How should I echo such a string? (the following command doesn't work)
echo '''#"`\|^!###%$#$^%'''$&%^*()?/\;:$#$"""#$?$$$!&&'''
Use printf:
$ printf "%s\n" $'#"`\|^!###%$#$^%$&%^*()?/\;:$#$#$?$$$!&&'
#"`\|^!###%$#$^%$&%^*()?/\;:$#$#$?$$$!&&
$ printf "%s\n" $'#"`\|^!###%$#$^%\'\'\'$&%^*()?/\;:$#$"""#$?$$$!&&'
#"`\|^!###%$#$^%'''$&%^*()?/\;:$#$"""#$?$$$!&&
You might note that single quotes ' need to be escaped.
In order to assign the output to a variable:
$ foo=$(printf "%s\n" $'#"`\|^!###%$#$^%\'\'\'$&%^*()?/\;:$#$"""#$?$$$!&&')
$ echo $foo
#"`\|^!###%$#$^%'''$&%^*()?/\;:$#$"""#$?$$$!&&
It is because shell applies all expansion rules inside a string double quotes ". $ or ! are special Unix characters to denote variable or event hence you get that error.
I think it is because of your shell (bash), which expands/interprets double quotes.
This does not apply for single quotes.
For details, please have a look at Bash - Shell Expansion.
For the echo command there is the -e option which enables interpretation of backslash escapes - which might help.

Resources