$?<variable> in sh - shell

Recently I come across below code ...
if [ ! $?target -o $target = "" ]; then
echo "Target is not set"
fi
I am curious to understand logic of above code snippet.
It seems that it is checking, if I am correct, whether "$taget" is set or not or "$target" is empty or not.
I google to find how $?variable works, but I didn't find any relative search results.
So please help me to understand how it works. Thanks in advance.

In some shells, csh for example, $?xyz will evaluate to 1 if xyz is set, otherwise it evaluates to 0.
All that snippet is doing is checking if either $target is not set at all or if it's set to an empty string. In both cases, it assumes it's not set.
Note that this is different from bash for example where $? is the return code from the last executed command. In that shell, you'll simply get the return code followed by the variable name, such as 0target.

I think your script is slightly mis-quoted,
if [ ! "$?target" -o "$target" = "" ]; then
echo "Target is not set"
fi
This will indeed print "Target is not set" when $target is unset, However A much more reliable method for doing this would be:-
if [ "x$foo" = "x" ]; then
echo "foo is not set"
fi

I put "#!sh" at the top, line 1 and ran it on a Mac OS 10.5.Terminal window, which says its bash...
The original script, with [ ! "$?target" -o "$target" = "" ]'
gets
try.sh: line 3: [: too many arguments
The version IanNorton suggests works:
Target is not set
I get the same results with #!bash on line 1, and no shell specification on line 1.
Your mileage may vary...

Related

Comparison sign as variable

Is it possible to set comparison sign as variable?
I want to do something like that:
var1=${1}
var2=${2}
condition=${3}
if [[ "${var1}" "${condition}" "${var2}" ]]; then
echo "Warning!"
fi
./alert.sh "10" "5" ">"
Setting it is no problem, it's using it that's difficult. (Your code raises a syntax error.)
You could switch from [[ ]] to test, though it's not exactly the same.
By the way, > is a string operator. You probably wanted -gt, the integer operator. See the link above for a table of operators.
var1="10"
var2="5"
condition="-gt"
if test "${var1}" "${condition}" "${var2}"; then
echo "Warning!"
fi
Output:
Warning!
Single square brackets also work (they're equivalent), but for some reason, ShellCheck detects a syntax error, so to make life easier with ShellCheck, I'd say just avoid it, even if that's a bug.
[ "${var1}" "${condition}" "${var2}" ]
An other way of doing such work is to construct & launch a second script.
It should be easier to debug (you can see & redo directly the cde)
echo "
#!/bin/bash
echo "es2 Start"
... write your cdes here ... with parameters you want
" > es2
chmod +x es2
./es2

Is [ $var ] an acceptable way to check for variables set/empty in bash?

I wrote some bash before reading this popular question. I noticed the way I was doing things doesn't show up in any of the answers so I was curious if there is a danger/bug to my logic that I am not seeing.
if [ ! $1 ] || [ $2 ]; then
echo "Usage: only one var." && exit 1
fi
As you can see, I am testing to see if there is one and only one parameter given on the command line. As far as I have tested, this works. Can someone pass along some more knowledge to a new bash user?
./script one '' oops
[ $2 ] will be false when $2 is the empty string.
And as that other guy points out in a comment, bash will split both strings, opening up a range of issues, including potential security holes.
This is clearer:
if [ $# != 1 ] ; then
echo "Usage: only one var."; exit 1
fi
Things that will break your test:
The first parameter is empty ("")
The second parameter is empty ("")
The first or second parameter has more words ("bla bla")
The parameters contain something that will be interpreted by test (e.g.: -z)
It is simply less clearer than using $#. Also the mere fact that I have to think about all the corner-cases which could potentially break the code makes it inferior.

Search for specific extension files (shellscript)

I am new to shell scripting, I have this script:
#!/bin/bash
path_file_conf=/fullpath/directory/*.conf
if [ -e "$path_file_conf" ];then
echo "Found file"
else
echo "No found file"
fi
The result is always "No found file" even if I have a .conf files inside /fullpath/directory/ folder.
May I know what part of the code is wrong?
Thanks in advance!
I would try something like this:
for filename in /fullpath/directory/*.conf
do
if [ -e "$filename" ] # If finds match...
then
echo "Found file"
echo
else
echo "No found file"
fi
done
I haven't tested so I'm not certain it works, but it will at least give you the overall strategy.
The expression:
path_file_conf=/fullpath/directory/*.conf
May have multiple path names that match. So the value of $path_file_conf may end up being, for example:
/fullpath/directory/foo1.conf /fullpath/directory/foo2.conf
The conditional:
if [ -e "$path_file_conf" ]; then
Checks for the existence of a single file. If "/fullpath/directory/foo1.conf /fullpath/directory/foo2.conf" doesn't name a "single file", which it won't, then the condition will fail even though the files exist.
You could check this way. If the path doesn't expand, it will fail and exit. If it finds at least one good path, it will succeed and exit.
for pf in $path_file_conf ; do
if [ -e "$pf" ] ; then
echo "Found"
break
else
echo "Not found"
fi
done
The line causing trouble is:
path_file_conf=/full/path/directory/*.conf
The shell does not do wild-card expansion on the name when there are multiple files to match, or when no files match, so (except in the unusual circumstance of having a file called *.conf with an asterisk) the -e test fails. There is probably an option in bash to generate an error when a wild card fails to match; I would never use it.
You can use:
path_file_conf=( /full/path/directory/*.conf )
This gives you an array with the names of the files as the elements of the array. However, if there are no files that match, it gives you the name as written as the only element of the array.
From there, you can check each file in turn:
for conf_file in "${path_file_conf[#]}"
do
if [ -e "$conf_file" ]
then echo "Found file $conf_file"
else echo "No such file as $conf_file"
fi
done
You can determine the number of names with ${#path_file_conf[#]}, but remember that 1 could indicate a real file or a non-existent file.

Creating a Bash command prompt with a red $ after failure of previous command

I'm new to Bash programming, and I'm working on creating a custom Bash command prompt. My goal is to create a prompt which only shows the login name and host name when they differ from what I normally use. I'm also looking to append the current Git branch to the command prompt when in a directory which is under Git version control.
I would like to color the login and host name section green, the directory path blue, the Git branch section pink, and separators (: and $ characters) white. However, when the previously executed command returns anything other than zero, I would like to color the $ separator red. The general format without colors looks like this:
loginname#hostname:~/current/path:branchname$
The only sections that are mandatory are the directory path and the $ character. Here's the code I've written for my .bashrc file:
MYNAME="gwen"
MYHOST="gwen-laptop"
RED="\[\033[31m\]"
WHITE="\[\033[0m\]"
GREEN="\[\033[01;32m\]"
BLUE="\[\033[01;34m\]"
PINK="\[\033[01;35m\]"
DOLLAR="if [ \$? = 0 ]; then echo ${WHITE}\$; else echo ${RED}\$${NORMAL}; fi"
GITBRN='$(__git_ps1 "\033[0m:\033[01;35m%s")'
USERNM="if [ \u != ${MYNAME} ]; then whoami; fi;"
HOSTNM="if [ \h != ${MYHOST} ]; then echo -n #; hostname; fi;"
COLONM="if [ \u != ${MYNAME} ] || [ \h != ${MYHOST} ]; then echo -n :; fi;"
PS1="${GREEN}\`${USERNM}\`\`${HOSTNM}\`${WHITE}\`${COLONM}\`${BLUE}\w${GITBRN}\`${DOLLAR}\` "
This code meets all of my requirements, except that it leaves the $ character white at all times, and does not color it red at the appropriate times. (I suspect the problem is that the "\$?" in DOLLAR references the previously executed command, but DOLLAR is executed last when constructing PS1, so the previously execute statement is no longer the command which was run before PS1 construction began; it's something which was executed in order to create the command prompt.) I'm not sure how to solve this problem.
This code is ugly and needs to be refactored. I was trying to move all the color codes into their own variables, but when I used these color variables in the code for GITBRN, thing went haywire, so I ended up using literal colors there instead.
I've spent an entire day trying to get this code working, and I think I'm going nowhere at this point. Any suggestions for how to get that dollar sign colored red at the appropriate time would be most appreciated. I'm also open to suggestion on refactoring the code to make it cleaner and more readable.
P.S. I'm a Ubuntu Linux (Lucid Lynx) user.
Use the PROMPT_COMMAND variable, which is executed before each primary prompt according to the bash man page.
For example (this doesn't work yet, I'm trying to get it working right, but I think it's possible):
PROMPT_COMMAND="if [ \$? = 0 ]; then DOLLAR="${WHITE}\$${NORMAL}"; else DOLLAR="${RED}\$${NORMAL}"; fi"
Edit: due to frustrations with executing commands and nonprinting characters inside PS1 (the \[ and \] sometimes get printed out literally instead of used as hints to PS1), I've come up with this (replace ... with whatever you want in your prompt):
PROMPT_COMMAND='if [ $? = 0 ]; then DOLLAR_COLOR="\033[0m"; else DOLLAR_COLOR="\033[31m"; fi'
PS1='...\[$(echo -ne $DOLLAR_COLOR)\]$\[\033[m\] '
Of course, using $() you could put whichever parts of this you like inside PS1 instead of using PROMPT_COMMAND, I just like it this way so that PROMPT_COMMAND contains the logic and PS1 contains the display commands.
I got it to work:
PROMPT_COMMAND='if [ $? = 0 ]; then PS1="\[\e[32;1m\]\u#\[\e[0m\e[30;47m\]\H\[\e[0m\]:\[\e[34;1m\]\w\[\e[0m\]$ "; else PS1="\[\e[31;1m\]\u#\[\e[0m\e[31;47m\]\H\[\e[0m\]:\[\e[31;1m\]\w\[\e[0m\]$ "; fi'
Standing on the sholders of #jtbandes. #jtbandes is the original author of the idea.
Here is Alexsandr's answer modified to display the red color only when a command fails and not when you push enter on an empty command line, as Stéphane requested.
trap 'PREVIOUS_COMMAND=$THIS_COMMAND; THIS_COMMAND=$BASH_COMMAND' DEBUG
read -r -d '' PROMPT_COMMAND << 'END'
if [ $? = 0 -o $? == 130 -o "$PREVIOUS_COMMAND" = ": noop" ]; then
PS1='\[\e[32;1m\]\u#\[\e[0m\e[30;47m\]\H\[\e[0m\]:\[\e[34;1m\]\w\[\e[0m\]$ '
else
PS1='\[\e[31;1m\]\u#\[\e[0m\e[31;47m\]\H\[\e[0m\]:\[\e[31;1m\]\w\[\e[0m\]$ '
fi
: noop
END

How to prevent code/option injection in a bash script

I have written a small bash script called "isinFile.sh" for checking if the first term given to the script can be found in the file "file.txt":
#!/bin/bash
FILE="file.txt"
if [ `grep -w "$1" $FILE` ]; then
echo "true"
else
echo "false"
fi
However, running the script like
> ./isinFile.sh -x
breaks the script, since -x is interpreted by grep as an option.
So I improved my script
#!/bin/bash
FILE="file.txt"
if [ `grep -w -- "$1" $FILE` ]; then
echo "true"
else
echo "false"
fi
using -- as an argument to grep. Now running
> ./isinFile.sh -x
false
works. But is using -- the correct and only way to prevent code/option injection in bash scripts? I have not seen it in the wild, only found it mentioned in ABASH: Finding Bugs in Bash Scripts.
grep -w -- ...
prevents that interpretation in what follows --
EDIT
(I did not read the last part sorry). Yes, it is the only way. The other way is to avoid it as first part of the search; e.g. ".{0}-x" works too but it is odd., so e.g.
grep -w ".{0}$1" ...
should work too.
There's actually another code injection (or whatever you want to call it) bug in this script: it simply hands the output of grep to the [ (aka test) command, and assumes that'll return true if it's not empty. But if the output is more than one "word" long, [ will treat it as an expression and try to evaluate it. For example, suppose the file contains the line 0 -eq 2 and you search for "0" -- [ will decide that 0 is not equal to 2, and the script will print false despite the fact that it found a match.
The best way to fix this is to use Ignacio Vazquez-Abrams' suggestion (as clarified by Dennis Williamson) -- this completely avoids the parsing problem, and is also faster (since -q makes grep stop searching at the first match). If that option weren't available, another method would be to protect the output with double-quotes: if [ "$(grep -w -- "$1" "$FILE")" ]; then (note that I also used $() instead of backquotes 'cause I find them much easier to read, and quotes around $FILE just in case it contains anything funny, like whitespace).
Though not applicable in this particular case, another technique can be used to prevent filenames that start with hyphens from being interpreted as options:
rm ./-x
or
rm /path/to/-x

Resources