Why can't I use $(...) in PS1 instead of backticks? - linux

My current PS1:
PS1='\[\033]0;$TITLEPREFIX:${PWD//[^[:ascii:]]/?}\007\]\n\[\033[32m\]\u#\h \[\033[35m\]`date +%Y-%m-%d,%H:%M:%S` \[\033[33m\]\w\[\033[36m\]`__git_ps1`\[\033[0m\]\n$: '
Yes, it's a mess, but it serves me well - my prompts look like this:
P2759474#RVPTINTCL415MQC 2017-10-06,11:20:18 ~/repos/jdk (master)
They are even color coded, with user#machine in green, timestamp in purple, current location in yellow, and any git branch in blue. I'm just a little annoyed that I have to use backticks instead of a $() construct.
Anyone know why? Willing to help me understand it? It's only a problem when parsing complex prompt values with subshell commands, and only a problem then because I want to understand why it matters there... General improvement suggestions always welcome while we're at it.
Update -
Currently when I try to use $() I get a lot of
bash: command substitution: line 1: syntax error near unexpected token ')'
bash: command substitution: line 1: 'date +%Y-%m-%d,%H:%M:%S)'
bash: command substitution: line 1: syntax error near unexpected token ')'
bash: command substitution: line 1: '__git_ps1)'
My env has
BASH_VERSINFO=([0]="4" [1]="3" [2]="42" [3]="5" [4]="release" [5]="x86_64-pc-msys")
BASH_VERSION='4.3.42(5)-release'
[ -z "$BASH_VERSION" ] || shopt -q promptvars || ps1_expanded=no;
That tells me something, maybe... Thanks!

When you start trying to embed commands in your prompt, it is time to start using PROMPT_COMMAND.
# You won't even have to put the title-bar stuff in your prompt
# and there are already shortcuts for date and time
set_titlebar () {
printf '\033]0;%s:%s\007' "$TITLEPREFIX" "${PWD//[^[:ascii:]]/?}"
}
set_prompt () {
PS1='\[\033[32m\]\u#\h ' # user#host in green
PS1+='\[\033[35m\]\D{%Y-%m-%d},\t ' # Don't need date
PS1+='\[\033[33m\]\w\[\033[36m\]' # Directory in orange
PS1+=$(__git_ps1) # git info, if appropriate
PS1+='\[\033[0m\]\n$: '
}
PROMPT_COMMAND='set_titlebar;set_prompt'

Related

Getting "ambiguous redirect" error in my shell script [duplicate]

The following line in my Bash script
echo $AAAA" "$DDDD" "$MOL_TAG >> ${OUPUT_RESULTS}
gives me this error:
line 46: ${OUPUT_RESULTS}: ambiguous redirect
Why?
Bash can be pretty obtuse sometimes.
The following commands all return different error messages for basically the same error:
$ echo hello >
bash: syntax error near unexpected token `newline`
$ echo hello > ${NONEXISTENT}
bash: ${NONEXISTENT}: ambiguous redirect
$ echo hello > "${NONEXISTENT}"
bash: : No such file or directory
Adding quotes around the variable seems to be a good way to deal with the "ambiguous redirect" message: You tend to get a better message when you've made a typing mistake -- and when the error is due to spaces in the filename, using quotes is the fix.
Do you have a variable named OUPUT_RESULTS or is it the more likely OUTPUT_RESULTS?
michael#isolde:~/junk$ ABC=junk.txt
michael#isolde:~/junk$ echo "Booger" > $ABC
michael#isolde:~/junk$ echo "Booger" >> $ABB
bash: $ABB: ambiguous redirect
michael#isolde:~/junk$
put quotes around your variable. If it happens to have spaces, it will give you "ambiguous redirect" as well. also check your spelling
echo $AAAA" "$DDDD" "$MOL_TAG >> "${OUPUT_RESULTS}"
eg of ambiguous redirect
$ var="file with spaces"
$ echo $AAAA" "$DDDD" "$MOL_TAG >> ${var}
bash: ${var}: ambiguous redirect
$ echo $AAAA" "$DDDD" "$MOL_TAG >> "${var}"
$ cat file\ with\ spaces
aaaa dddd mol_tag
I've recently found that blanks in the name of the redirect file will cause the "ambiguous redirect" message.
For example if you redirect to application$(date +%Y%m%d%k%M%S).log and you specify the wrong formatting characters, the redirect will fail before 10 AM for example. If however, you used application$(date +%Y%m%d%H%M%S).log it would succeed. This is because the %k format yields ' 9' for 9AM where %H yields '09' for 9AM.
echo $(date +%Y%m%d%k%M%S) gives 20140626 95138
echo $(date +%Y%m%d%H%M%S) gives 20140626095138
The erroneous date might give something like:
echo "a" > myapp20140626 95138.log
where the following is what would be desired:
echo "a" > myapp20140626095138.log
Does the path specified in ${OUPUT_RESULTS} contain any whitespace characters? If so, you may want to consider using ... >> "${OUPUT_RESULTS}" (using quotes).
(You may also want to consider renaming your variable to ${OUTPUT_RESULTS})
If your script's redirect contains a variable, and the script body defines that variable in a section enclosed by parenthesis, you will get the "ambiguous redirect" error. Here's a reproducible example:
vim a.sh to create the script
edit script to contain (logit="/home/ubuntu/test.log" && echo "a") >> ${logit}
chmod +x a.sh to make it executable
a.sh
If you do this, you will get "/home/ubuntu/a.sh: line 1: $logit: ambiguous redirect". This is because
"Placing a list of commands between parentheses causes a subshell to
be created, and each of the commands in list to be executed in that
subshell, without removing non-exported variables. Since the list is
executed in a subshell, variable assignments do not remain in effect
after the subshell completes."
From Using parenthesis to group and expand expressions
To correct this, you can modify the script in step 2 to define the variable outside the parenthesis: logit="/home/ubuntu/test.log" && (echo "a") >> $logit
I got this error when trying to use brace expansion to write output to multiple files.
for example: echo "text" > {f1,f2}.txt results in -bash: {f1,f2}.txt: ambiguous redirect
In this case, use tee to output to multiple files:
echo "text" | tee {f1,f2,...,fn}.txt 1>/dev/null
the 1>/dev/null will prevent the text from being written to stdout
If you want to append to the file(s) use tee -a
If you are here trying to debug this "ambiguous redirect" error with GitHub Actions. I highly suggest trying it this way:
echo "MY_VAR=foobar" >> $GITHUB_ENV
The behavior I experienced with $GITHUB_ENV is that, it adds it to the pipeline environment variables as my example shows MY_VAR
I just had this error in a bash script. The issue was an accidental \ at the end of the previous line that was giving an error.
One other thing that can cause "ambiguous redirect" is \t \n \r in the variable name you are writing too
Maybe not \n\r? But err on the side of caution
Try this
echo "a" > ${output_name//[$'\t\n\r']}
I got hit with this one while parsing HTML, Tabs \t at the beginning of the line.
This might be the case too.
you have not specified the file in a variable and redirecting output to it, then bash will throw this error.
files=`ls`
out_file = /path/to/output_file.t
for i in `echo "$files"`;
do
content=`cat $i`
echo "${content} ${i}" >> ${out_file}
done
out_file variable is not set up correctly so keep an eye on this too.
BTW this code is printing all the content and its filename on the console.
if you are using a variable name in the shell command, you must concatenate it with + sign.
for example :
if you have two files, and you are not going to hard code the file name, instead you want to use the variable name
"input.txt" = x
"output.txt" = y
then ('shell command within quotes' + x > + y)
it will work this way especially if you are using this inside a python program with os.system command probably
In my case, this was a helpful warning, because the target variable (not the file) was misspelled and did not exist.
echo "ja" >> $doesNotExist
resulting in
./howdy.sh: line 4: $doesNotExist: ambiguous redirect
For my case, if I specify the output file via a env (e.g $ENV_OF_LOG_FILE), then will get the error ambiguous redirect.
But, if I use plain text as file path (e.g /path/to/log_file), then there is no error.

Internal Variable PIPESTATUS

I am new to linux and bash scripting and i have query about this internal variable PIPESTATUS which is an array and stores the exit status of individual commands in pipe.
On command line:
$ find /home | /bin/pax -dwx ustar | /bin/gzip -c > myfile.tar.gz
$ echo ${PIPESTATUS[*]}
$ 0 0 0
working fine on command line but when I am putting this code in a bash script it is showing only one exit status. My default SHELL on command line is bash only.
Somebody please help me to understand why this behaviour is changing? And what should I do to get this work in script?
#!/bin/bash
cmdfile=/var/tmp/cmd$$
backfile=/var/tmp/backup$$
find_fun() {
find /home
}
cmd1="find_fun | /bin/pax -dwx ustar"
cmd2="/bin/gzip -c"
eval "$cmd1 | $cmd2 > $backfile.tar.gz " 2>/dev/null
echo -e " find ${PIPESTATUS[0]} \npax ${PIPESTATUS[1]} \ncompress ${PIPESTATUS[2]} > $cmdfile
The problem you are having with your script is that you aren't running the same code as you ran on the command line. You are running different code. Namely the script has the addition of eval. If you were to wrap your command line test in eval you would see that it fails in a similar manner.
The reason the eval version fails (only gives you one value in PIPESTATUS) is because you aren't executing a pipeline anymore. You are executing eval on a string that contains a pipeline. This is similar to executing /bin/bash -c 'some | pipe | line'. The thing actually being run by the current shell is a single command so it has a single exit code.
You have two choices here:
Get rid of eval (which you should do anyway as eval is generally something to avoid) and stop using a string for a command (see Bash FAQ 050 for more on why doing this is a bad idea.
Move the echo "${PIPESTATUS[#]}" into the eval and then capture (and split/parse) the resulting output. (This is clearly a worse solution in just about every way.)
Instead of ${PIPESTATUS[0]} use ${PIPESTATUS[#]}
As with any array in bash PIPESTATUS[0] contains the first command exit status. If you want to get all of them you have to use PIPESTATUS[#] which returns all the contents of the array.
I'm not sure why it worked for you when you tried it in the command line. I tested it and I didn't get the same result as you.

How to show line number when executing bash script

I have a test script which has a lot of commands and will generate lots of output, I use set -x or set -v and set -e, so the script would stop when error occurs. However, it's still rather difficult for me to locate which line did the execution stop in order to locate the problem.
Is there a method which can output the line number of the script before each line is executed?
Or output the line number before the command exhibition generated by set -x?
Or any method which can deal with my script line location problem would be a great help.
Thanks.
You mention that you're already using -x. The variable PS4 denotes the value is the prompt printed before the command line is echoed when the -x option is set and defaults to : followed by space.
You can change PS4 to emit the LINENO (The line number in the script or shell function currently executing).
For example, if your script reads:
$ cat script
foo=10
echo ${foo}
echo $((2 + 2))
Executing it thus would print line numbers:
$ PS4='Line ${LINENO}: ' bash -x script
Line 1: foo=10
Line 2: echo 10
10
Line 3: echo 4
4
http://wiki.bash-hackers.org/scripting/debuggingtips gives the ultimate PS4 that would output everything you will possibly need for tracing:
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
In Bash, $LINENO contains the line number where the script currently executing.
If you need to know the line number where the function was called, try $BASH_LINENO. Note that this variable is an array.
For example:
#!/bin/bash
function log() {
echo "LINENO: ${LINENO}"
echo "BASH_LINENO: ${BASH_LINENO[*]}"
}
function foo() {
log "$#"
}
foo "$#"
See here for details of Bash variables.
PS4 with value $LINENO is what you need,
E.g. Following script (myScript.sh):
#!/bin/bash -xv
PS4='${LINENO}: '
echo "Hello"
echo "World"
Output would be:
./myScript.sh
+echo Hello
3 : Hello
+echo World
4 : World
Workaround for shells without LINENO
In a fairly sophisticated script I wouldn't like to see all line numbers; rather I would like to be in control of the output.
Define a function
echo_line_no () {
grep -n "$1" $0 | sed "s/echo_line_no//"
# grep the line(s) containing input $1 with line numbers
# replace the function name with nothing
} # echo_line_no
Use it with quotes like
echo_line_no "this is a simple comment with a line number"
Output is
16 "this is a simple comment with a line number"
if the number of this line in the source file is 16.
This basically answers the question How to show line number when executing bash script for users of ash or other shells without LINENO.
Anything more to add?
Sure. Why do you need this? How do you work with this? What can you do with this? Is this simple approach really sufficient or useful? Why do you want to tinker with this at all?
Want to know more? Read reflections on debugging
Simple (but powerful) solution: Place echo around the code you think that causes the problem and move the echo line by line until the messages does not appear anymore on screen - because the script has stop because of an error before.
Even more powerful solution: Install bashdb the bash debugger and debug the script line by line
If you're using $LINENO within a function, it will cache the first occurrence. Instead use ${BASH_LINENO[0]}

How do I fix unexpected end of file in bash script?

I had much luck last time I submitted a question so here goes: I am trying to debug a somewhat large BASH script when I get the following error:
./test.sh: line 418: unexpected EOF while looking for matching `"'
./test.sh: line 427: syntax error: unexpected end of file
The code below starts at line 400:
echo "###########################################################"
echo
;;
4)
culebra_carriers
get_month
get_day
logs_cdrs
logs_wap
get_mdn
echo
echo "###########################################################"
echo
echo "Searching for activity of $mobileNumber on $MON $DAY......."
echo
zgrep $mobileNumber $HOME/culebrapeak/$LOGCDR/$CULEB/$MON/$WAPLOG
echo
echo "###########################################################"
echo
;;
esac
done
}
clear
main_menu
How do I make this error go away? It looks like I have the double quotes in all the right places... but this is only my 4th or 5th bash script... so please go easy on me.
I was, indeed, missing a double quote at the top of my script. Thanks to all for the help!
A good way to solve problems like this is to use a text editor that highlights code between quotes. Short of that, if the "find" feature of your text editor gives a count too, you may be able to use it to quantitatively detect start/end character symmetry problems. The highlighting from the find feature will aide your eye tremedously.

Bash Shell - The : Command

The colon command is a null command.
The : construct is also useful in the conditional setting of variables. For example,
: ${var:=value}
Without the :, the shell would try to evaluate $var as a command. <=???
I don't quite understand the last sentence in above statement. Can anyone give me some details?
Thank you
Try
var=badcommand
$var
you will get
bash: badcommand: command not found
Try
var=
${var:=badcommand}
and you will get the same.
The shell (e.g. bash) always tries to run the first word on each command line as a command, even after doing variable expansion.
The only exception to this is
var=value
which the shell treats specially.
The trick in the example you provide is that ${var:=value} works anywhere on a command line, e.g.
# set newvar to somevalue if it isn't already set
echo ${newvar:=somevalue}
# show that newvar has been set by the above command
echo $newvar
But we don't really even want to echo the value, so we want something better than
echo ${newvar:=somevalue}.
The : command lets us do the assignment without any other action.
I suppose what the man page writers meant was
: ${var:=value}
Can be used as a short cut instead of say
if [ -z "$var" ]; then
var=value
fi
${var} on its own executes the command stored in $var. Adding substitution parameters does not change this, so you use : to neutralize this.
Try this:
$ help :
:: :
Null command.
No effect; the command does nothing.
Exit Status:
Always succeeds.

Resources