I am studying the book "Beginning Linux Programming 4th ed" and chapter 2 is about shell programming. I was impressed by the example on page 53, and tried to develop a script to display more on that. Here is my code:
enter code here
#!/bin/bash
var1=10
var2=20
var3=30
var4=40
for i in 1 2 3 4 # This works as intended!
do
x=var$i
y=$(($x))
echo $x = $y # But we can avoid declaring extra parameters x and y, see next line
printf " %s \n" "var$i = $(($x))"
done
for j in 1 2 3 4 #This has problems!
do
psword=PS$j
#eval psval='$'PS$i # Produces the same output as the next line
eval psval='$'$psword
echo '$'$psword = $psval
#echo "\$$psword = $psval" #The same as previous line
#echo $(eval '$'PS${i}) #Futile attempts
#echo PS$i = $(($PS${i}))
#echo PS$i = $(($PS{i}))
done
#I can not make it work as I want : the output I expect is
#PS1 = \[\e]0;\u#\h: \w\a\]${debian_chroot:+($debian_chroot)}\u#\h:\w\$
#PS2 = >
#PS3 =
#PS4 = +
How can I get the intended output? When I run it as it is I only get
PS1 =
PS2 =
PS3 =
PS4 = +
What happened with PS1 and PS2 ?
Why do not I get the same value that I get with
echo $PS1
echo $PS2
echo $PS3
echo $PS4
because that was what I am trying to get.
Shell running a script is always non interactive shell. You may force to run the script in interactive mode using '-i' option:
Try to change:
#!/bin/bash
to:
#!/bin/bash -i
see INVOCATION section in 'man bash' (bash.bashrc is where your PS1 is defined):
When an interactive shell that is not a login shell is started, bash reads and executes commands from
/etc/bash.bashrc and ~/.bashrc, if these files exist. This may be inhibited by using the --norc option. The
--rcfile file option will force bash to read and execute commands from file instead of /etc/bash.bashrc and
~/.bashrc.
When bash is started non-interactively, to run a shell script, for example, it looks for the variable BASH_ENV in
the environment, expands its value if it appears there, and uses the expanded value as the name of a file to read
and execute. Bash behaves as if the following command were executed:
if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
but the value of the PATH variable is not used to search for the file name.
you can also read: http://tldp.org/LDP/abs/html/intandnonint.html
simple test:
$ cat > test.sh
echo "PS1: $PS1"
$ ./test.sh
PS1:
$ cat > test.sh
#!/bin/bash -i
echo "PS1: $PS1"
$ ./test.sh
PS1: ${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u#\h\[\033[01;34m\] \w \$\[\033[00m\]
Use indirect expansion:
for j in 0 1 2 3 4; do
psword="PS$j"
echo "$psword = ${!psword}"
done
Related
I need to run the program NACCESS on my MacOS X High Sierra.
The most prominent error is if: Expression Syntax.
This error is probably because the NACCESS executable is a CSH script with #!/bin/csh on its first line.
I changed the first line to #!/bin/bash and began getting errors such as
./naccess: line 14: syntax error near unexpected token `1'
./naccess: line 14: ` exit(1)'
Again, I think this is happening because the executable needs to be executed in a CSH shell, not bash. exit(1) is formatted for the CSH shell, whereas bash would want exit 1, according to this resource.
I tried typing csh into Terminal to switch shells, but an echo $SHELL command just tells me I'm still in bash.
I looked at my shell choices with grep '^#!' /usr/bin/* and cannot find any CSH shells. I attempted to download tcsh using homebrew. But I really have no idea what I'm doing at this point.
I know CSH shells aren't encouraged, but how do I get a CSH shell up and running on my Mac, or how do I otherwise get NACCESS to run?
[EDIT] Here are the first few lines of the NACCESS code:
(Note that I am running it with the .pdb file argument and also that I have set path_to_naccess_repository appropriately. I do get the usage naccess pdb_file [-p probe_size] [-r vdw_file] [-s stdfile] [-z zslice] -[hwyfaclqb] readout if I run only ./naccess)
#!/bin/csh
set EXE_PATH = /path/to/repo
#naccess_start
set nargs = $#argv
if ( $nargs < 1 ) then
echo "usage naccess pdb_file [-p probe_size] [-r vdw_file] [-s stdfile] [-z zslice] -[hwyfaclqb]"
exit(1)
endif
set PDBFILE = 0
set VDWFILE = 0
set STDFILE = 0
set probe = 1.40
set zslice = 0.05
set hets = 0
set wats = 0
set hyds = 0
set full = 0
set asao = 0
set cont = 0
set oldr = 0
set nbac = 0
while ( $#argv )
switch ($argv[1])
case -[qQ]:
echo "Naccess2.1 S.J.Hubbard June 1996"
echo "Usage: naccess pdb_file [-p probe_size] [-r vdw_file] [-s stdfile] [-z zslice] -[hwyfaclq]"
echo " "
echo "Options:"
echo " -p = probe size (next arg probe size)"
echo " -z = accuracy (next arg is accuracy)"
echo " -r = vdw radii file (next arg is filename)"
echo " -s = standard accessibilities file (next arg is filename)"
echo " -h = hetatoms ?"
echo " -w = waters ?"
echo " -y = hydrogens ?"
echo " -f = full asa output format ?"
echo " -a = produce asa file only, no rsa file ?"
echo " -c = output atomic contact areas in asa file instead of accessible areas"
echo " -l = old RSA output format (long)"
echo " -b = consider alpha carbons as backbone not sidechain"
echo " -q = print the usage line and options list"
echo " "
exit
breaksw
case -[pP]:
shift
set probe = $argv[1]
breaksw
case -[zZ]:
shift
set zslice = $argv[1]
breaksw
case -[hH]:
set hets = 1
breaksw
case -[wW]:
set wats = 1
breaksw
case -[yY]:
set hyds = 1
breaksw
case -[rR]:
shift
set VDWFILE = $argv[1]
breaksw
case -[sS]:
shift
set STDFILE = $argv[1]
breaksw
case -[fF]:
set full = 1
breaksw
case -[aA]:
set asao = 1
breaksw
case -[cC]:
set cont = 1
breaksw
case -[lL]:
set oldr = 1
breaksw
case -[bB]:
set nbac = 1
breaksw
default:
if ( -e $argv[1] && $PDBFILE == 0 ) then
set PDBFILE = $argv[1]
endif
breaksw
endsw
shift
end
#
if ( $PDBFILE == 0 ) then
echo "usage: you must supply a pdb format file"
exit(1)
endif
[EDIT] I did NOT set the path appropriately >.< So this question is mainly about how to get a csh script running in bash. There are no problems with this csh script.
The SHELL variable simply lists your first login shell, it will not change if you run another shell. echo $0 will show you you are running csh. Your options are either to stay in bash and change the shebang to tcsh:
#!/usr/bin/tcsh -f
or remove the shebang completely and run
csh ./naccess
If you are going for the first option, and not sure where csh is, just hit
which csh
to find the path. As the #Chepner adds, since the file comes with a csh shebang, don't touch it and just run it as above.
If the first line of a script starts with #!, called a "shebang", that specifies how the script is executed if you invoke it as a command. It gives the full path of the interpreter to invoke and, optionally, an argument to pass to the interpreter, along with the script name.
If you have a script called foo whose first line is
#!/bin/csh
then typing ./foo is equivalent to /bin/csh foo. (The file foo has to be executable; use chmod +x if it isn't already.)
This is completely independent of (a) your login shell, (b), the shell you're currently running, and (c) the shell specified by the $SHELL environment variable.
You can invoke a script by explicitly typing the name of the shell used to run it, but the whole point of the #! is that you don't have to do that.
It's possible that /bin/csh doesn't exist on your system, or that it's something other than the C-shell.
tcsh is an updated version of csh. You should be able to use it to execute any csh script.
Find out where csh or tcsh is installed on your system and update the path in the #! line to refer to it. You might need to install either csh or tcsh yourself -- but you said you typed csh and it worked, so that shouldn't be necessary.
From a bash shell, the command type csh will tell you where csh is installed.
Incidentally, the #! line for a csh or tcsh script should include a -f option:
#!/bin/csh -f
This tells the shell not to source the user's startup script ($HOME/.cshrc) when running the script. It saves time and ensures that the script is portable, not dependent on one user's environment. (This does not apply to sh or bash scripts; sh and bash have a -f option, but it has a completely different meaning.)
I tried typing csh into Terminal to switch shells, but an echo $SHELL
command just tells me I'm still in bash.
Invoking a shell doesn't change the value of your $SHELL environment variable. It's normally set to the path to your default login shell (but it can be changed).
You can tell if you're running tcsh by typing echo $tcsh or echo $version. (csh doesn't have these variables). You can tell if you're running bash by typing echo $BASH_VERSION.
I have two scripts. Script A includes script B and calls a function in script B.
The setup looks like this:
Test file - ~/file.txt
one==1.0.0
two==2.0.0
three==3.0.0
four==4.0.0
Script A - ~/script_a.sh
#!/bin/bash
source script_b.sh
func_one
Script B - ~/script_b.sh
#!/bin/bash
# Note: don't forget to change the spaces to tabs else heredoc won't work
my_user=$USER
func_two() {
# Here, I need run everything in the heredoc as user $my_user
sudo su - $my_user -s /bin/bash <<- EOF
while read -r line || [[ -n "$line" ]];
do
# **This is the problem line**
# I can confirm that all the lines are being
# read but echo displays nothing
echo "$line"
# The line below will be printed 4 times as there are 4 lines in the file of interest
echo "Test"
done < "/home/$my_user/file.txt"
EOF
}
func_one() {
func_two
}
To run
cd ~
bash script_a.sh
Question: Why is the line echo "$line" not producing any output?
The problem is that bash is substituting $line with its value (nothing) before it gets passed to su. Escaping the dollar sign should fix it. So $line should be changed to \$line in both places in script_b.sh.
This question already has answers here:
How can I execute a command stored in a variable?
(4 answers)
Closed 2 years ago.
I am writing a script, and one part of it is not working as I would expect.
I have broken out this part in a simple example for simplicity:
echo 'echo "" > tmp' | while read cmd; do $cmd ; done
Here I would expect the full command, "echo "" > tmp" to be executed by $cmd.
But this happens:
"" > tmp
The command executed echoes out "" > tmp literally. Instead of echoing out "" and redirecting it to the file tmp. Obviously something is wrong when storing the command in $cmd and then later trying to execute it.
The result is the same even if I simplify it further:
cmd="echo "" > tmp"
$cmd
> tmp
I have tried to experiment with different usages of '' and "", but not solved it yet.
Use eval to execute the command stored in the variable:
echo 'echo "" > tmp' | while read cmd; do eval "$cmd" ; done
The value of cmd will be echo "" > tmp. Then when Bash resolves the parameter substitution as a command, the part "" > tmp will be the string arguments of echo, not be recognized as >(redirection). So it will just output the arguments part.
The same as: $(echo 'echo "" > tmp')
Change
do $cmd ;
to
do eval "$cmd" ;
I'm trying to customize my bash prompt and I'm having trouble with a few conditionals.
My current PS1 looks like this.
export PS1="\
$PS1USERCOLOR\u\
$COLOR_WHITE#\
$COLOR_GREEN\h\
$COLOR_WHITE:\
$COLOR_YELLOW\W\
\`if type parse_git_branch > /dev/null 2>&1; then parse_git_branch; fi\`\
\`if [ \$? = 0 ]; then echo -e '$COLOR_WHITE'; else echo -e '$COLOR_RED'; fi\`\$\
$COLOR_WHITE"
The first 6 lines just set regular PS1 stuff.
Line 7 then calls a function to display the current git branch and status if applicable.
Line 8 then tests the return code of the previous command and changes the colour of the $ on the end.
Line 9 sets the prompt back to white ready for the user's command.
However line 8 is responding to the return code from line 7's function and not the previous command as I first expected.
I've tried moving line 8 before line 7 and eveything works as it should. But I don't want line 8 before line 7, the $ must be on the end.
I've tried setting a variable earlier on to be the value of $? and then testing that variable like so
export PS1="\
\`RETURN=\$?\`\
$PS1USERCOLOR\u\
$COLOR_WHITE#\
$COLOR_GREEN\h\
$COLOR_WHITE:\
$COLOR_YELLOW\W\
\`if type parse_git_branch > /dev/null 2>&1; then parse_git_branch; fi\`\
\`if [ \$RETURN = 0 ]; then echo -e '$COLOR_WHITE'; else echo -e '$COLOR_RED'; fi\`\$\
$COLOR_WHITE"
But this doesn't work.
Does anybody have any idea how to solve my problem?
The proper way is to use PROMPT_COMMAND like so:
prompt_cmd () {
LAST_STATUS=$?
PS1="$PS1USERCOLOR\u"
PS1+="$COLOR_WHITE#"
PS1+="$COLOR_GREEN\h"
PS1+="$COLOR_WHITE:"
PS1+="$COLOR_YELLOW\W"
if type parse_git_branch > /dev/null 2>&1; then
PS1+=$(parse_git_branch)
fi
if [[ $LAST_STATUS = 0 ]]; then
PS1+="$COLOR_WHITE"
else
PS1+="$COLOR_RED"
fi
PS1+='\$'
PS1+="$COLOR_WHITE"
}
Since PROMPT_COMMAND is evaluated prior to each prompt, you simply execute code that sets PS1 they way you like for each prompt instance, rather than trying to embed deferred logic in the string itself.
A couple of notes:
You must save $? in the first line of the code, before the value you want is overwritten.
I use double quotes for most of the steps, except for \$; you could use PS1+="\\\$" if you like.
The standard solution to this problem is to make use of the bash environment variable PROMPT_COMMAND. If you set this variable to the name of a shell function, said function will be executed before each bash prompt is shown. Then, inside said function, you can set up whatever variables you want. Here's how I do almost exactly what you're looking for in my .bashrc:
titlebar_str='\[\e]0;\u#\h: \w\a\]'
time_str='\[\e[0;36m\]\t'
host_str='\[\e[1;32m\]\h'
cwd_str='\[\e[0;33m\]$MYDIR'
git_str='\[\e[1;37m\]`/usr/bin/git branch --no-color 2> /dev/null | /bin/grep -m 1 ^\* | /bin/sed -e "s/\* \(.*\)/ [\1]/"`\[\e[0m\]'
dolr_str='\[\e[0;`[ $lastStatus -eq 0 ] && echo 32 || echo 31`m\]\$ \[\e[0m\]'
export PS1="$titlebar_str$time_str $host_str $cwd_str$git_str$dolr_str"
function prompt_func {
# Capture the exit status currently in existence so we don't overwrite it with
# any operations performed here.
lastStatus=$?
# ... run some other commands (which will have their own return codes) to set MYDIR
}
export PROMPT_COMMAND=prompt_func
Now bash will run prompt_func before displaying each new prompt. The exit status of the preceding command is captured in lastStatus. Because git_str, dolr_str, etc. are defined with single quotes, the variables (including lastStatus) and commands inside them are then re-evaluated when bash dereferences PS1.
Solved it!
I need to use the PROMPT_COMMAND variable to set the RETURN variable. PROMPT_COMMAND is a command which is called before PS1 is loaded.
My script now looks like
PROMPT_COMMAND='RETURN=$?'
export PS1="\
$PS1USERCOLOR\u\
$COLOR_WHITE#\
$COLOR_GREEN\h\
$COLOR_WHITE:\
$COLOR_YELLOW\W\
\`if type parse_git_branch > /dev/null 2>&1; then parse_git_branch; fi\`\
\`if [[ \$RETURN = 0 ]]; then echo -e '$COLOR_WHITE'; else echo -e '$COLOR_RED'; fi\`\$\
$COLOR_WHITE"
I am writing a Bash script where I want all commands to be echoed as they occur. I know that I need to use set -x and set +x to toggle this behaviour off and on, respectively (SOF post here). However, it doesn't echo everything, namely, I/O redirects.
For example, let's say I have this very simple Bash script:
set -x
./command1 > output1
./command2 arg1 arg2 > output2
This will be the output
+ ./command1
+ ./command2 arg1 arg2
Bash is not echoing my stdout redirect to output1 and output2. Why is this? How can I achieve this behaviour? Perhaps is there a shopt option that I must set in the script?
NOTE: I also noticed that pipes will not print as expected. For example, if I were to use this command:
set -x
./command3 | tee output3
I will get this output:
+ tee output3
+ ./command3
How do I make the commands be echoed exactly in the way they are written instead of having the pipe reordered by the script?
Neither set -x nor the DEBUG trap will provide full information when certain constructs are used.
Examples are:
ex1.sh
for i in a b c
do
echo $i # Where does the output go?
done > outfile # Here!
ex2.sh
c="this"
case "$c" in
this) echo sure ;; # Where does the output go?
that) echo okay ;;
esac > choice # Here!
ex3.sh
what="up"
while [ "$what" = "up" ]
do
echo "Going down!" # Where does the output go?
what="down"
echo "Newton rulezzz!" > thelaw
done > trying # Here!
and many more like these, let alone all kinds of nested variants. I don't know of an easy way to handle these, except to enter into the land of full Bash script parsing, which is a minefield...
EDIT: If Bash-specific features are not required, and backwards compatibility with the Bourne shell will do, the Korn shell (ksh, tested with version 93u+ 2012-08-01) does a bit better on showing information for redirects:
$ ksh -x ex1.sh
+ 1> outfile
+ echo a
+ echo b
+ echo c
$ ksh -x ex2.sh
+ c=this
+ 1> choice
+ echo sure
$ ksh -x ex3.sh
+ what=up
+ 1> trying
+ [ up '=' up ]
+ echo 'Going down!'
+ what=down
+ echo 'Newton rulezzz!'
+ 1> thelaw
+ [ down '=' up ]
You should rather use set -v.
-v Print shell input lines as they are read.
The output seems to meet your expectation.
$ set -v
$ ./command1 > output1
./command1 > output1
sh: ./command1: No such file or directory
$ ./command2 arg1 arg2 > output2
./command2 arg1 arg2 > output2
sh: ./command2: No such file or directory
$ ./command3 | tee output3
./command3 | tee output3
sh: ./command3: No such file or directory
It isn't possible with set -x. Your only option is to view the current command through a DEBUG trap.
trap 'printf %s\\n "$BASH_COMMAND" >&2' DEBUG
This won't show the precise arguments as set -x will. Combining them should give you the full picture, though it's a lot of debug output.