Making a bash script switch to interactive mode and give a prompt - bash

I am writing a training tool, it is written in bash to teach bash/unix.
I want a script to run to set things up, then to hand control to the user.
I want it to be easily runnable by typing ./script-name
How do I do this?
I.E.
User types: tutorial/run
The run-tutorial script sets things up.
The user is presented with a task. (this bit works)
The command prompt is returned, with the shell still configured.
Currently it will work if I type . tutorial/bashrc

There are several options:
You start script in the same shell, using source or .;
You start a new shell but with your script as a initialization script:
The first is obvious; I write a little bit more details about the second.
For that, you use --init-file option:
bash --init-file my-init-script
You can even use this option in the shebang line:
#!/bin/bash --init-file
And then you start you script as always:
./script-name
Example:
$ cat ./script-name
#!/bin/bash --init-file
echo Setting session up
PS1='.\$ '
A=10
$ ./script-name
Setting session up
.$ echo $A
10
.$ exit
$ echo $A
$
As you can see, the script has made the environment for the user and then has given him the prompt.

Try making it an alias in your ~/.bashrc file. Add this to the bottom of ~/.bashrc:
alias tutorial='. tutorial/bashrc'
Then close and re-open your terminal, or type . ~/.bashrc to re-source it.
To use this alias, simply call tutorial, and that will automatically get replaced with its alias, as though you had called . tutorial/bashrc.

Related

.bashrc file is looping "script" command

I'm trying to set up a little shell script using the linux command "script" to log every input on my Kali Linux machine.
#!/bin/bash
now=$(date +"%m_%d_%Y_%H:%M:%S")
script /root/Logs/log_$now.txt
The script itself seems to work but i want to add it to the bash autostart, so whenever i open a terminal, my shellscript gets executed.
I tried adding it to my .bashrc file but when I open a terminal now, the script gets looped.
I added a simple "echo 'test'" script and it only starts once on terminal launch.
Adding the script to my .profile file and executing .profile manually works as intended, but as soon as i enter a script using the "script" command to my .bashrc, it gets looped.
Thank you in advance.
A new terminal window is one way of starting a new interactive shell, but so is running script. You only want to run script in the first case, not in every case.
script itself sets a variable in the environment to let you know if you are already in a shell started by script. Check for that variable before trying to run script again.
if [[ -z $SCRIPT ]]; then
now=$(date +"%m_%d_%Y_%H:%M:%S")
script /root/Logs/log_$now.txt
fi
The value of SCRIPT, if set, is the name of the file being logged to.
Alternatively, you can configure your terminal emulator to run script directly, rather than having it continue to open an ordinary interactive shell and you trying to alter its configuration.
The above applies to BSD script; for GNU script, you'll have to set such a variable yourself.
if [[ -z $SCRIPT ]]; then
now=$(date +"%m_%d_%Y_%H:%M:%S")
export SCRIPT=/root/Logs/log_$now.txt
script "$SCRIPT"
fi
The script(1) command opens a new interactive shell.
The file .bashrc runs on every interactive bash shell that is started, hence your infinite recursion.
If you want something to run only on the login shell, you put it into .bash_profile.
This should avoid the infinite recursion.

bash shell in Cygwin

when I am in a Cygwin terminal, I can easily use the "source" command.
For example, let's say I have a file called "my_aliases.sh", that contains the following
#!/bin/bash -f
alias clear='cmd /c cls'
#unalias clear
Then on the Cygwin terminal, I can type
$source my_aliases.sh
And it just works fine, and whenever I type "clear", I can see that it works.
But I don't know why doing the same thing inside another shell script, and calling that shell script doesn't work.
For example, let's say that I have a file called "run_alias.sh", with the following content:
#!/bin/bash -f
#
a=`source my_aliases.sh`
b=`ls -ltr`
echo $a
echo $b
And when I try to run my file
$ ./run_alias.sh
It just doesn't do anything. For example, I can see that the command (b) takes place, but nothing happens for command (a).
But after I run "run_alias.sh", and type "clear", I get the following error:
$ clear
bash: clear: command not found
I even tried to change run_alias.sh as follows:
#!/bin/bash -f
echo `source my_aliases.sh`
But now when run run_alias.sh, and type clear, I get the exact same error message !!!
Any idea how to call the "source" command from some other shell script in Cygwin?
A child process cannot alter its parent's environment.
When you execute the run_alias.sh script, you launch a new bash process, which sources your alias file. Then the script ends, that bash process terminates and it takes its modified environment with it.
If you want your aliases to be automatically available, source it from your $HOME/.bashrc file.
Backticks create a subshell. The changes made to your environment in that subshell do not affect the calling environment.
Id you want your script (run_alias.sh) to have access to the environment in my_aliases.sh, call source directly.
source my_aliases.sh
b=`ls -lrt`
echo $b
and if you want the changes that run_alias.sh makes to its environment to propagate to it's parent, run source on the command line.
$ source run_alias.sh

Is it possible to specify the bash prompt using a command-line option?

I use vim a lot and often find it useful to drop into the command line using !bash.
However, I need to type exit to return to vim and sometimes I'm not sure whether I'm in a subshell or whether that will close my session.
What I'd really like to do is type something like !bash -prompt "subshell" so that I get something like this:
subshell$ <commands go here>
Is this possible?
The most direct way to do this is to set the PS1 environment variable within vim:
:let $PS1="subshell$ "
And start your sub-shells using the command :shell instead of :!bash.
Using the $ sign with let modifies an environment variable. Add this to your .vimrc to persist the setting.
Alternately, using :shell you can specify a more specific command, including arguments, using the shell option, see help shell and help 'shell'.
So:
:set shell=bash\ --rcfile\ ~/.vimbashrc
In .vimbashrc add PS1="subshell ", and invoke the sub-shells using :shell instead of !bash. Add this to your .vimrc to persist the setting.
So you have two options:
Add let $PS1="subshell " to your .vimrc, and start sub-shells using :shell instead of :!bash.
Make a custom rc file for your vim sub-shells, add your specific PS1="subshell " to it, and modify the shell option in your .vimrc: set shell=bash\ --rcfile\ ~/.vimbashrc.
Finally, if you must use :!bash to start the sub-shells there are a couple of more options. Note that you can also pass a more specific command line using !, e.g.:
:PS1="subshell$ " bash should work.
:!bash\ --rcfile\ ~/.vimbashrc, and set PS1 in .vimbashrc as above
But you'll need to type these every time, or define a mapping for it.
Use shell variable $SHLVL seems to be another option here. Add $SHLVL in your $PS1:
export PS1="$PS1 $SHLVL"
so your prompt looks like this:
[tim#RackAblade47 ~]$ 2
when you start shell from VIM, $SHLVL will increase:
[tim#RackAblade47 ~]$ 4
Yes - you can change the prompt inside the shell before running your commands
PS1="subshell"
checkout this guide for all the options http://www.linuxselfhelp.com/howtos/Bash-Prompt/Bash-Prompt-HOWTO-2.html
If you really must do it via the bash command you can use '--rcfile' to specify an RC file that runs the PS1 command for you (you usually put the PS1= line your .bashrc to customize the prompt at login)
To answer your original question, you can say inside Vim:
:!VIMPROMPT="(vim) " bash
and change your prompt (in your .bashrc, presumably) from something like
PS1='\u#\h:\w\$ '
to
PS1='$VIMPROMPT\u#\h:\w\$ '
this will change your prompt from
me#host:~$
to
(vim) me#host:~$
if run inside Vim.
I'm personally using
case $(ps $PPID) in *vim|*bash)
PS1="$(ps $PPID | awk '{print $NF}' | sed 1d) $PS1" ;;
esac
in my prompt script that's sourced by my .bashrc, taken from triplee's comment.
I have been able to change the prompt for a vim subshell process by checking for the MYVIMRC variable which is exported inside vim, and will then update PS1 accordingly. I updated my .bashrc file with the following.
PS1='\$ '
# when in vim subshell change PS1 for clarity
if [[ $MYVIMRC ]]; then PS1='>> '; fi;
Put this in your .bashrc after any existing PS1= statements.
if ps | grep -q vim; then
export PS1="[VIM]$PS1"
fi
Tested on Ubuntu.
You can send it to the background with CTRL+Z and then bring it back with the fg command. With jobs you see all the jobs you have stopped.
This was you can have multiple instances of vim in parallel and chose which one you want to bring back. If there's no running you will just get a no current job error and that's it.
This doesn't specifically answer your question, but addresses the problem underneath it.

Changing bash prompt in new bash

When I create an new bash process, the prompt defaults to a very simple one.
I know I can edit .bashrc etc to change this, but is there a way of passing the prompt with the bash command?
thanks!
The prompt is defined by the PS1, PS2, PS3 and PS4 environment variables. So, e.g. the following will start a new bash with the prompt set to "foo: ":
PS1="foo: " bash --norc
The --norc is required to suppress processing of the initialization files, which would override the PS1 variable.
I have the same problem - I'd like to startup a temporary bash from the command line; and while most other environment variables remain; those that are sourced from ~/.bashrc are kind of difficult to override - especially if you, like me, would actually like to keep the ~/.bashrc you already have (and aliases inside, etc.) - save for the PS1 prompt.
Here is something that works for me (note that --init-file is a synonym/alias for --rcfile):
bash --rcfile <(cat ~/.bashrc ; echo 'PS1="\[\033[0;33m\]\u#HELLO:\W\$\[\033[00m\] "')
Basically, the bracket/less-than + parenthesis idiom <() starts up bash process substitution; everything echoed to stdout inside the parenthesis will end up in a temporary file, /dev/fd/<n>. So we first cat the contents of out ~/.bashrc; then we simply add a PS1 set command at end, (which effectively overrides) - this ends up in /dev/fd/<n>; and bash then uses /dev/fd/<n> for the new rcfile.
This is how it behaves:
user#pc:tmp$ TESTVAR="testing" bash --rcfile <(cat ~/.bashrc ; echo 'PS1="\[\033[0;33m\]\u#HELLO:\W\$\[\033[00m\] "')
user#HELLO:tmp$ test-alias-tab-completion ^C
user#HELLO:tmp$ echo $TESTVAR
testing
user#HELLO:tmp$ exit
exit
user#pc:tmp$
You can set an environment variable, and then use that environment variable in your prompt in .bashrc.
If you want the profiles, but don't want to hardcode all the possible paths to source them from, then a more generic somewhat shell-agnostic solution is:
PROMPT_COMMAND='PS1="(customize) $PS1"; PROMPT_COMMAND=' $SHELL
With the caveat of only working as long as the default profiles don't set PROMPT_COMMAND. But that's more likely than them not setting PS1.

Using aliases with nohup

Why doesn't the following work?
$ alias sayHello='/bin/echo "Hello world!"'
$ sayHello
Hello world!
$ nohup sayHello
nohup: appending output to `nohup.out'
nohup: cannot run command `sayHello': No such file or directory
(the reason I ask this question is because I've aliased my perl and python to different perl/python binaries which were optimized for my own purposes; however, nohup gives me troubles if I don't supply full path to my perl/python binaries)
Because the shell doesn't pass aliases on to child processes (except when you use $() or ``).
$ alias sayHello='/bin/echo "Hello world!"'
Now an alias is known in this shell process, which is fine but only works in this one shell process.
$ sayHello
Hello world!
Since you said "sayHello" in the same shell it worked.
$ nohup sayHello
Here, a program "nohup" is being started as a child process. Therefore, it will not receive the aliases.
Then it starts the child process "sayHello" - which isn't found.
For your specific problem, it's best to make the new "perl" and "python" look like the normal ones as much as possible. I'd suggest to set the search path.
In your ~/.bash_profile add
export PATH="/my/shiny/interpreters/bin:${PATH}"
Then re-login.
Since this is an environment variable, it will be passed to all the child processes, be they shells or not - it should now work very often.
For bash: Try doing nohup 'your_alias'. It works for me. I don't know why back quote is not shown. Put your alias within back quotes.
With bash, you can invoke a subshell interactively using the -i option. This will source your .bashrc as well as enable the expand_aliases shell option. Granted, this will only work if your alias is defined in your .bashrc which is the convention.
Bash manpage:
If the -i option is present, the shell is interactive.
expand_aliases: If set, aliases are expanded as described above under ALIASES. This option is enabled by default for interactive shells.
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.
$ nohup bash -ci 'sayHello'
If you look at the Aliases section of the Bash manual, it says
The first word of each simple command, if unquoted, is checked to see
if it has an alias.
Unfortunately, it doesn't seem like bash has anything like zsh's global aliases, which are expanded in any position.

Resources