How to generate PS1 in a function (escaping issues) - bash

I'd like to move my bash prompt's construction into a function that can build it up modularly. The problem is that I cannot figure out how to get the function's result to be interpreted.
Example:
function build_prompt {
echo "\#"
}
export PS1="\$(build_prompt)"
My prompt always shows as \#, but should be the current time.
Sure there are ways around this particular example, but I'd like a general solution so I can use it for other escaped components, such as colours.

This is one use case for the PROMPT_COMMAND variable: running a function just before displaying the prompt that updates the value of PS1.
function build_prompt {
PS1='\#'
}
PROMPT_COMMAND='build_prompt'

Related

Why does function call from PS1 require escaping?

I'm setting my prompt inside of .bash_profile like this
export PS1="\w\$(getBranchName)\n ---->"
My getBranchName function exists, and this works, fine.
My question is, why do I need to escape the call to getBranchName like this \$(getBranchName).
In other words, why doesn't this code work, instead?
export PS1="\w$(getBranchName)\n ---->"
If curious, this is what the getBranchName function looks like
esc="\033"
redf="${esc}[31m"
green="${esc}[32m"
purple="${esc}[35m"
cyanf="${esc}[36m"
reset="${esc}[0m"
getBranchName() {
if [[ "$(__git_ps1 '%s')" == "master" ]]
then
echo -e "${redf}$(__git_ps1)${reset}";
else
echo -e "${cyanf}$(__git_ps1)${reset}";
fi
}
export PS1="\w\$(getBranchName)\n ---->"
You need to escape the dollar because you want to store this exact text in your variable.
Try it by typing echo "$PS1". You should see the exact text : \w$(getBranchName)\n ---->
If you didn't escape it, the function would be evaluated only once, during the allocation.
The bottom line is that PS1 is a special variable : every time you display a new line in the console, the variable is evaluated to extract the display settings.
The PS1 variable is basically a template string (which might contain function calls) which is evaluated each time the prompt is shown.
If you want to evaluate a function each time, so that each prompt shows the result of this new execution, you need to escape the call.
If you would embed the function call directly in the string, the function would be called once immediately (i.e. likely during login) and your PS1 will contain the result of this single function call as evaluated during your initial login. Thus, the value won't be updated again since the function is not called anymore (since the PS1 doesn't contain the function call anymore but only the static result of one).
It's escaped because you want it to run when the shell evaluates $PS1 each time it's displayed, not just during the assignment.
The other expansions (which should be using tput unless you actually like random control codes all over your non-ANSI terminals) you want to be expanded just once, when you assign to PS1.

How do I pass a command parameter in a variable holding the command?

I want to produce the same output as this:
bash utilities.bash "is_net_connected"
But I don't know how to pass "is_net_connected" if command and file is stored in a variable like this:
T=$(bash utilities.bash)
I've tried these but it doesn't seem to work. It's not picking up ${1} in utilities.bash.
$(T) "is_net_connected"
$(T "is_net_connected")
Not the best way to inport but I'm trying to avoid cluttering my main script with function blocks.
T=$(bash utilities.bash) doesn't save the command; it runs the command and saves its output. You want to define a function instead.
T () {
bash utilities.bash "$#"
}
# Or on one line,
# T () { bash utilities.bash "$#"; }
Now
T "is_net_connected"
will run bash utilities.bash with whatever arguments were passed to T. In a case like this, an alias would work the same: alias T='bash utilities.bash'. However, any changes to what T should do will probably require switching from an alias to a function anyway, so you may as well use the function to start. (Plus, you would have to explicitly enable alias expansion in your script.)
You might be tempted to use
T="bash utilities.bash"
$T is_net_connected
Don't be. Unquoted parameter expansions are bad practice that only work in select situations, and you will get bitten eventually if you try to use them with more complicated commands. Use a function; that's why the language supports them.

Export serialized array in bash

I'm trying to serialize an array in bash and then export it:
function serialize
{
for i in ${1[#]}; do
ret+=$i" "
done
return ${ret::-1}
}
MEASUREMENT_OUTPUT_FILES=( t1DQ.txt t1CS.txt t2RXe.txt t2e.txt )
export MEASUREMENT_OUTPUT_FILES=${serialize MEASUREMENT_OUTPUT_FILES[#]}
The code produces the following error:
MEASUREMENT_OUTPUT_FILES=${serialize MEASUREMENT_OUTPUT_FILES[#]}: bad
substitution
Any ideas what the correct syntax (error in the last line starting with export) would be?
I believe you want:
export MEASUREMENT_OUTPUT_FILES=$(serialize "${MEASUREMENT_OUTPUT_FILES[#]}")
(where $(...) is a notation for command substitution).
That said, your command is actually equivalent to
export MEASUREMENT_OUTPUT_FILES="${MEASUREMENT_OUTPUT_FILES[*]}"
so you don't need the serialize function unless you want to improve your serialization logic. (Which you should consider doing, IMHO: just joining with a space is error-prone, because what if one of the arguments includes a space?)
Edited to add: Also, I don't know how we all missed this before, but this:
return ${ret::-1}
actually needs to be this:
echo "${ret::-1}"
or this:
printf %s "${ret::-1}"
since return is for setting the exit status of a function, which must be an integer. (It's intended for indicating success, zero, vs. failure, nonzero, though some commands assign special meanings to multiple nonzero values.) What you want is for your function to "print" the files, so you can capture them.

Bash functions returning values meanwhile altering global variables

I'm just struggling with bash functions, and trying to return string values meanwhile some global variable is modified inside the function. An example:
MyGlobal="some value"
function ReturnAndAlter () {
MyGlobal="changed global value"
echo "Returned string"
}
str=$(ReturnAndAlter)
echo $str # prints out 'Returned value' as expected
echo $MyGlobal # prints out the initial value, not changed
This is because $(...) (and also `...` if used instead) cause the function to have its own environment, so the global variable is never affected.
I found a very dirty workaround by returning the value into another global variable and calling the function only using its name, but think that there should be a cleaner way to do it.
My dirty solution:
MyGlobal="some value"
ret_val=""
function ReturnAndAlter () {
ret_val="Returned string"
MyGlobal="changed value"
}
ReturnAndAlter # call the bare function
str=$ret_val # and assign using the auxiliary global ret_val
echo $str
echo $MyGlobal # Here both global variables are updated.
Any new ideas? Some way of calling functions that I'm missing?
Setting global variables is the only way a function has of communicating directly with the shell that calls it. The practice of "returning" a value by capturing the standard output is a bit of a hack necessitated by the shell's semantics, which are geared towards making it easy to call other programs, not making it easy to do things in the shell itself.
So, don't worry; no, you aren't missing any cool tricks. You're doing what the shell allows you to do.
The $(…) (command expansion) is run in a sub-shell.
All changes inside the sub-shell are lost when the sub-shell close.
It is usually a bad idea to use both printing a result and changing a variable inside a function. Either make all variables or just use one printed string.
There is no other solution.

Special Character in Export Variable

I have a variable in my shell script that needs to declared as follows:
MY_VAR="/path/to/exec -options < inputfile"
This is the standard way the executable takes the input. Now, if I do $MY_VAR, the program quits with an error too many arguments. I suspect it is the < sign that is causing the problem. Any way I can get a workaround without splitting the statement into two variables?
Thanks
You could use eval, but that is considered harmful (see BashFAQ). Try to find a better solution that does not need the whole command line in a variable. For example, use a function:
my_func() {
/path/to/exec -options < inputFile
}

Resources