Conditional ZSH_THEME by terminal - terminal

I'm trying to make that when .zshrc when invoked chooses a specific theme based on which terminal amulator i'm running. For this I came up with:
current_terminal="$(ps -p$PPID -o cmd=)"
function choose_theme {
if [ $current_terminal=~'tilix' ];
then echo 'powerlevel9k/powerlevel9k';
else echo 'robbyrussell';
fi
}
ZSH_THEME="$(choose_theme)"
I don't get any error message when running and when I open on tilix it works just fine with the powerlevel9k theme, but just that, it doesn't seem to respect the condition and I don't know where might my mstake be =/
The output for the variable current_terminal in each terminal emulator i'm using are:
Tilix:
/usr/bin/tilix --gapplication-service
Default Terminal:
/usr/lib/gnome-terminal/gnome-terminal-server
So it's getting things wright, but setting up always the first option for some reason

This does not work due to two reasons:
You are using [ ... ] instead of [[ ... ]] for the check. The difference is that [[ ... ]] is part of the ZSH's syntax and [ (aka test) is a built-in command that tries to emulate the external test program. This matters because [ does not support the =~ operator, it is only available inside [[ ... ]].
The =~ (like any other operator) needs to be surrounded by white spaces. As a Unix shell ZSH tokenizes command on white spaces. In this case ZSH I would guess that ZSH only checks whether $current_terminal=~'tilix' evaluates to a non-empty string instead of comparing $current_terminal to 'tilix'. This is always the case, hence why you always get the powerlevel9k theme.
So the condition should look like this:
current_terminal="$(ps -p$PPID -o cmd=)"
function choose_theme {
if [[ $current_terminal =~ 'tilix' ]];
then echo 'powerlevel9k/powerlevel9k';
else echo 'robbyrussell';
fi
}
ZSH_THEME="$(choose_theme)"

Related

Ubuntu bash script string contains similar words

I am trying to write a bash script that will tell whether two strings are of similar value. I have produced this bash script:
#!/bin/bash
value="java.lang.NullPointerException"
if [[ "java.lang.NullPointerException" = "$value" || "java.lang.NullPointerException" == "$value"* ]]; then
echo "Match"
fi
Basically what I want to achive, is that if two strings are of equal value or a very similar either side but with matching text in the middle then echo "Match".
I have tried a number of resources but can't get this example to work. I have taken a look at:
In bash, how can I check if a string begins with some value?
How to test that a variable starts with a string in bash?
https://ubuntuforums.org/showthread.php?t=1118003
Please note these values would eventually come from a text file and so the values will be in a form of variables. I have tried different approaches, but don't seem to get it working. I just want to get this if statement working. It works for matching text but not for values either side. Value could be "java.lang.NullPointerException: Unexpected" or "Unexpected java.lang.NullPointerException".
#!/bin/bash
value="java.lang.NullPointerException" #or java.lang.NullPointerException: Unexpected
if [[ $value == *"java.lang.NullPointerException"* ]];
then
echo "Match"
fi
A simple and portable (POSIX compliant) technique for wildcard matching is to use a case statement rather than if. For your example, this would look something like
#!/bin/sh
value="java.lang.NullPointerException"
case "$value" in
*java.lang.NullPointerException*) echo Match;;
esac

Why am I getting a 'unary operator expected' error?

I'm writing a shell script to streamline my development workflow.
It takes an argument as to which theme folder I'm going to be working in and starts grunt watch on that directory.
If I call the script without the necessary argument I'm currently printing a warning that a theme needs to be specified as a command line argument.
I'd like to print a list of the available options, e.g. theme directories
This is what I have so far...
THEME=$1
if [ $THEME == '' ]
then
echo 'Need to specify theme'
else
cd 'workspace/aws/ghost/'$THEME'/'
grunt watch
fi
Ideally I'd replace the output of the echo line with an ls of the themes parent directory like so
THEME=$1
if [ $THEME == '' ]
then
echo 'Need to specify theme from the following'
ls workspace/aws/ghost
else
cd 'workspace/aws/ghost/'$THEME'/'
grunt watch
fi
However this gives me the following error
./ghost_dev.sh: line 3: [: ==: unary operator expected
You need quotes around $THEME here:
if [ $THEME == '' ]
Otherwise, when you don't specify a theme, $THEME expands to nothing, and the shell sees this syntax error:
if [ == '' ]
With quotes added, like so:
if [ "$THEME" == '' ]
the expansion of an empty $THEMEyields this valid comparison instead:
if [ "" == '' ]
This capacity for runtime syntax errors can be surprising to those whose background is in more traditional programming languages, but command shells (at least those in the Bourne tradition) parse code somewhat differently. In many contexts, shell parameters behave more like macros than variables; this behavior provides flexibility, but also creates traps for the unwary.
Since you tagged this question bash, it's worth noting that there is no word-splitting performed on the result of parameter expansion inside the "new" test syntax available in bash (and ksh/zsh), namely [[...]]. So you can also do this:
if [[ $THEME == '' ]]
The places you can get away without quotes are listed here. But it's a fine habit to always quote parameter expansions anyway except when you explicitly want word-splitting (and even then, look to see if arrays will solve your problem instead).
It would be more idiomatic to use the -z test operator instead of equality with the empty string:
if [ -z "$THEME" ]
You technically don't need the quotation marks in this simple case; [ -z ] evaluates to true. But if you have a more complicated expression, the parser will get confused, so it's better to just always use the quotes. Of course, [[...]] doesn't require any here, either:
if [[ -z $THEME ]]
But [[...]] is not part of the POSIX standard; for that matter, neither is ==. So if you care about strict compatibility with other POSIX shells, stick to the quoting solution and use either -z or a single =.
[ "$THEME" ] will evaluate to false if $THEME is undefined or an empty string and true otherwise. See http://www.gnu.org/software/bash/manual/html_node/Bash-Conditional-Expressions.html#Bash-Conditional-Expressions. You can rearrange your if statement to exploit this behavior and have an even simpler conditional:
if [ "$THEME" ]; then
cd 'workspace/aws/ghost/'$THEME'/'
grunt watch
else
echo 'Need to specify theme from the following'
ls workspace/aws/ghost
fi
"$THEME" needs to be in double-quotes, in case its value contains whitespace.
Please correct the syntax with the double quotes.
if [ "$THEME" == "" ]; then
echo 'Need to specify theme from the following'
ls workspace/aws/ghost
fi

String contains in Bash that is a directory path

I am writing an SVN script that will export only changed files. In doing so I only want to export the files if they don't contain a specific file.
So, to start out I am modifying the script found here.
I found a way to check if a string contains using the functionality found here.
Now, when I try to run the following:
filename=`echo "$line" |sed "s|$repository||g"`
if [ ! -d $target_directory$filename ] && [[!"$filename" =~ *myfile* ]] ; then
fi
However I keep getting errors stating:
/home/home/myfile: "no such file or directory"
It appears that BASH is treating $filename as a literal. How do I get it so that it reads it as a string and not a path?
Thanks for your help!
You have some syntax issues (a shell script linter can weed those out):
You need a space after "[[", otherwise it'll be interpretted as a command (giving an error similar to what you posted).
You need a space after the "!", otherwise it'll be considered part of the operand.
You also need something in the then clause, but since you managed to run it, I'll assume you just left it out.
You combined two difference answers from the substring thing you posted, [[ $foo == *bar* ]] and [[ $foo =~ .*bar.* ]]. The first uses a glob, the second uses a regex. Just use [[ ! $filename == *myfile* ]]

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