perl how to finish the script by 'cd $newdir' - bash

I have a perl script that creates a directory $newdir based on some input passed as a parameter, and I would like the script to finish it's execution by doing:
cd $newdir
So that the next command in bash Linux 64bit (here program2) is executed from the $newdir working directory.
E.g.:
perl $HOME/import_script.pl -i someparameter && $HOME/program2 .

You can't.
Any cd (or similar) you run in the perl script will affect only the perl script (or a sub-shell spawned from the perl script).
It can't affect the parent shell directly.
The only thing you could do would be to output the directory and then cd to that or similar. (e.g. cd "$(perl "$HOME"/import_script.pl -i someparameter)" && "$HOME/program2" . but realize that this means you can't output anything else to standard output from the perl script or it will confuse cd.)
Or have perl run the second command also, etc.

Just adding another potential solution here; you can have your perl script output the bash you want to run, and run it with bash eval. For example;
File: do.pl:
#!/usr/bin/env perl
use strict;
use warnings;
use File::Temp qw(tempdir);
my $dir = tempdir();
print "echo Whatever I print out will be evaluated in my shell;";
print "cd $dir"; # Separate multiple commands with ';', see ^
From Bash
[~] > eval `./do.pl`
[~] > Whatever I print out will be evaluated in my shell
[/tmp/BcZI6ZaRB] > _
You can make things even easier by adding an alias, or bash function to your shell.
Add to File: ~/.bashrc
doit() {
eval $(./do.pl)
}
From Bash
[~] > doit
[~] > Whatever I print out will be evaluated in my shell
[/tmp/ejzVGauPXx] > _

Related

Use echo options when execution shell script

On mac OSX, I have this script:
#!/usr/local/bin/bash
echo -e "\e[41mError: Some error.\e[0m"
When I just run the echo -e ... in a console, it prints the colored text "Error: Some error."
When executed as a script sh myscript.sh, it litterally prints the flag and the escape characters: -e "\e[41mError: Some error.\e[0m".
When I add the script location to ~/.bash_profile and execute it as myscript.sh, it does work. But I need to be able execute it without adding it to my bash profile.
Edit: using printf works: printf "\e[41mError: Some error.\e[0m\n".
when you run the shell with sh it runs in posix compatibility mode (i.e. as the bourne shell does)
bash is a successor to this shell, one of the features it adds is the -e switch to echo
in posix shell you don't need the -e, the escapes will be evaluated anyway
in bash you do, so if you want to run bash do so explicitly

How to embed and run a multi-line perl script stored in a bash variable in a bash script (without immediately running perl) [duplicate]

This question already has answers here:
Run Perl Script From Unix Shell Script
(2 answers)
Closed 3 years ago.
How do I replace [RUN_ABOVE_PERL_SORTING_SCRIPT_HERE] with something that runs this perl script stored in a bash variable?
#!/usr/bin/env bash
# The perl script to sort getfacl output:
# https://github.com/philips/acl/blob/master/test/sort-getfacl-output
find /etc -name .git -prune -o -print | xargs getfacl -peL | [RUN_ABOVE_PERL_SORTING_SCRIPT_HERE] > /etc/.facl.nogit.txt
Notes:
I do not want to employ 2 files (a bash script and a perl script) to solve this problem; I want the functionality to be stored all in one bash script file.
I do not want to immediately run the perl script when storing the perl-script variable, because I want to run it later in the getfacl(1) bash pipeline shown below.
There's many similar stackoverflow questions+answers, but none that I can find (that has clean-reading code, anyway?) that solve both the a) multi-line and b) delayed-execution (or the embedded perl script) portion of this problem.
And to clarify: this problem is not specifically about getfacl(1), which is simply an catalyst to explore how to embed perl scripts--and possibly other scripting languages like python--into bash variables for delayed execution in a bash script.)
Employ the bash read command, which reads the perl script into a variable that's executed later in the bash script.
#!/usr/bin/env bash
# sort getfacl output: the following code is copied from:
# https://github.com/philips/acl/blob/master/test/sort-getfacl-output
read -r -d '' SCRIPT <<'EOS'
#!/usr/bin/env perl -w
undef $/;
print join("\n\n", sort split(/\n\n/, <>)), "\n\n";
EOS
find /etc -name .git -prune -o -print | xargs getfacl -peL | perl -e "$SCRIPT" > /etc/.facl.nogit.txt
This is covered by Run Perl Script From Unix Shell Script.
As they apply here:
You can pass the code to Perl using -e/-E.
perl -e"$script"
or
perl -e"$( curl "$url" )"
You can pass the code via STDIN.
printf %s "$script" | perl -e"$script"
or
curl "$url" | perl
(This won't work for you because you need STDIN.)
You can create a virtual file.
perl <( printf %s "$script" )
or
perl <( curl "$url" )
You can take advantage of perl's -x option.
(Not applicable if you want to download the script dynamically.)
All of the above assume the following command has already been executed:
url='https://github.com/philips/acl/blob/master/test/sort-getfacl-output'
Some of the above assume the following command has already been executed:
script="$( curl "$url" )

How do I run the same file with multiple shebang interpreter?

So suppose I want to run some commands in bash and then say, octave. Is there any way to run the same file with two different commands? In the example below I want the first part to be run by bash and the second to be run by octave
#!/bin/bash
echo helloooo
#!/bin/octave
plot(1,2)
pause()
There is only one shebang possible at first line of a script.
Not sure of what you want to do here, but you can use the script's name to switch the script interpreter at run-time
Exemple myscript.sh
#!/usr/bin/env bash
case "${0##*.}" in
# Switch script's interpreter based on script's name trailing .extension
sh)
echo helloooo
;;
plot | octave | oct)
octave < <(
# Remove the Bash part of this script
# by deleting lines up to what look like an octave shebang,
# and send it to octave
sed '1,/^#!.*[/[:space:]]\+octave$/d' "$0"
)
;;
esac
exit # Bash stuffs ends here
# Octave stuffs starts here with fake octave shebang
#!/usr/bin/env octave
plot(1,2)
pause()
Then create a link to your script:
ln --symbolic --force myscript.sh myscript.oct
Running it as a Bash script:
./myscript.sh
helloooo
Running as an Octave script:
./myscript.oct
...
You could of course just by-pass the bash script altogether and do output in octave itself. It's a neater solution.
#!/usr/bin/octave
disp('hellooo')
plot(1,2)
pause()

"source" in ruby subshells

I need to run a shell command from a ruby application. I'm using system() but this also applies to backticks.
When running my command, I need to load a shell script first that sets up some things so I try something like this:
system("source my_script.sh && my_command")
On my Mac laptop this works as intended but on my ubuntu server I get:
sh: 1: source: not found
I was wondering about the "sh" in there since my shell should be a bash, so tried this:
system("echo $SHELL && source my_script.sh && my_command")
Which gives me:
/bin/bash
sh: 1: source: not found
So, it is using the right shell but for some reason, source does not work.
Why? And what can I do about it?
Update
As Sergio Tulentsev pointed out, Ruby does not necessarily use the shell that is set in $SHELL.
This gave me the actual shell that ruby was using:
system("ps -p $$ | tail -1 | awk '{print $NF}'")
sh
=> true
So, it's using sh. Can I somehow force it to use bash?
You need to try adding ./ in front of the file you want to source, that should work if the subshell is bash (check $SHELL).
irb(main):003:0> system("source ./test.sh && echo $TEST && cat test.sh")
test
export TEST=test
=> true
If $SHELL is sh, then you need to do . ./test.sh instead of source ./test.sh, as the source keyword is bash only.
Or you can make sure that you are using bash, by doing:
irb(main):007:0> system("/bin/bash -c 'source ./test.sh && echo $TEST && cat test.sh'")
test
export TEST=test
=> true
As others have pointed out, Ruby uses sh for its subshells. One way to make it use bash would be something like system("/bin/bash -c '...'") which leads to all kinds of escaping problems. Instead I decided to use Open3 to spawn a "real" process, run bash in it and pipe my commands into it. Works like a charm:
require "open3"
# using bash --login to ensure the same env as usual
Open3.popen3('/usr/bin/env bash --login') do |stdin, stdout, stderr, wait_thr|
pid = wait_thr[:pid]
stdin.puts("cd some_directory")
stdin.puts("source some_script")
stdin.puts("some_command")
# don't forget to close it again
stdin.puts("exit")
# for debug purposes
stdout.each_line do |line|
puts "STDOUT: " + line
end
stdin.close
stdout.close
stderr.close
end
This may seem like a little overkill but the control it allows over the child process is actually pretty nice.
Thanks everybody for your suggestions.

How to invoke bash, run commands inside the new shell, and then give control back to user?

This must either be really simple or really complex, but I couldn't find anything about it... I am trying to open a new bash instance, then run a few commands inside it, and give the control back to the user inside that same instance.
I tried:
$ bash -lic "some_command"
but this executes some_command inside the new instance, then closes it. I want it to stay open.
One more detail which might affect answers: if I can get this to work I will use it in my .bashrc as alias(es), so bonus points for an alias implementation!
bash --rcfile <(echo '. ~/.bashrc; some_command')
dispenses the creation of temporary files. Question on other sites:
https://serverfault.com/questions/368054/run-an-interactive-bash-subshell-with-initial-commands-without-returning-to-the
https://unix.stackexchange.com/questions/123103/how-to-keep-bash-running-after-command-execution
This is a late answer, but I had the exact same problem and Google sent me to this page, so for completeness here is how I got around the problem.
As far as I can tell, bash does not have an option to do what the original poster wanted to do. The -c option will always return after the commands have been executed.
Broken solution: The simplest and obvious attempt around this is:
bash -c 'XXXX ; bash'
This partly works (albeit with an extra sub-shell layer). However, the problem is that while a sub-shell will inherit the exported environment variables, aliases and functions are not inherited. So this might work for some things but isn't a general solution.
Better: The way around this is to dynamically create a startup file and call bash with this new initialization file, making sure that your new init file calls your regular ~/.bashrc if necessary.
# Create a temporary file
TMPFILE=$(mktemp)
# Add stuff to the temporary file
echo "source ~/.bashrc" > $TMPFILE
echo "<other commands>" >> $TMPFILE
echo "rm -f $TMPFILE" >> $TMPFILE
# Start the new bash shell
bash --rcfile $TMPFILE
The nice thing is that the temporary init file will delete itself as soon as it is used, reducing the risk that it is not cleaned up correctly.
Note: I'm not sure if /etc/bashrc is usually called as part of a normal non-login shell. If so you might want to source /etc/bashrc as well as your ~/.bashrc.
You can pass --rcfile to Bash to cause it to read a file of your choice. This file will be read instead of your .bashrc. (If that's a problem, source ~/.bashrc from the other script.)
Edit: So a function to start a new shell with the stuff from ~/.more.sh would look something like:
more() { bash --rcfile ~/.more.sh ; }
... and in .more.sh you would have the commands you want to execute when the shell starts. (I suppose it would be elegant to avoid a separate startup file -- you cannot use standard input because then the shell will not be interactive, but you could create a startup file from a here document in a temporary location, then read it.)
bash -c '<some command> ; exec /bin/bash'
will avoid additional shell sublayer
You can get the functionality you want by sourcing the script instead of running it. eg:
$cat script
cmd1
cmd2
$ . script
$ at this point cmd1 and cmd2 have been run inside this shell
Append to ~/.bashrc a section like this:
if [ "$subshell" = 'true' ]
then
# commands to execute only on a subshell
date
fi
alias sub='subshell=true bash'
Then you can start the subshell with sub.
The accepted answer is really helpful! Just to add that process substitution (i.e., <(COMMAND)) is not supported in some shells (e.g., dash).
In my case, I was trying to create a custom action (basically a one-line shell script) in Thunar file manager to start a shell and activate the selected Python virtual environment. My first attempt was:
urxvt -e bash --rcfile <(echo ". $HOME/.bashrc; . %f/bin/activate;")
where %f is the path to the virtual environment handled by Thunar.
I got an error (by running Thunar from command line):
/bin/sh: 1: Syntax error: "(" unexpected
Then I realized that my sh (essentially dash) does not support process substitution.
My solution was to invoke bash at the top level to interpret the process substitution, at the expense of an extra level of shell:
bash -c 'urxvt -e bash --rcfile <(echo "source $HOME/.bashrc; source %f/bin/activate;")'
Alternatively, I tried to use here-document for dash but with no success. Something like:
echo -e " <<EOF\n. $HOME/.bashrc; . %f/bin/activate;\nEOF\n" | xargs -0 urxvt -e bash --rcfile
P.S.: I do not have enough reputation to post comments, moderators please feel free to move it to comments or remove it if not helpful with this question.
With accordance with the answer by daveraja, here is a bash script which will solve the purpose.
Consider a situation if you are using C-shell and you want to execute a command
without leaving the C-shell context/window as follows,
Command to be executed: Search exact word 'Testing' in current directory recursively only in *.h, *.c files
grep -nrs --color -w --include="*.{h,c}" Testing ./
Solution 1: Enter into bash from C-shell and execute the command
bash
grep -nrs --color -w --include="*.{h,c}" Testing ./
exit
Solution 2: Write the intended command into a text file and execute it using bash
echo 'grep -nrs --color -w --include="*.{h,c}" Testing ./' > tmp_file.txt
bash tmp_file.txt
Solution 3: Run command on the same line using bash
bash -c 'grep -nrs --color -w --include="*.{h,c}" Testing ./'
Solution 4: Create a sciprt (one-time) and use it for all future commands
alias ebash './execute_command_on_bash.sh'
ebash grep -nrs --color -w --include="*.{h,c}" Testing ./
The script is as follows,
#!/bin/bash
# =========================================================================
# References:
# https://stackoverflow.com/a/13343457/5409274
# https://stackoverflow.com/a/26733366/5409274
# https://stackoverflow.com/a/2853811/5409274
# https://stackoverflow.com/a/2853811/5409274
# https://www.linuxquestions.org/questions/other-%2Anix-55/how-can-i-run-a-command-on-another-shell-without-changing-the-current-shell-794580/
# https://www.tldp.org/LDP/abs/html/internalvariables.html
# https://stackoverflow.com/a/4277753/5409274
# =========================================================================
# Enable following line to see the script commands
# getting printing along with their execution. This will help for debugging.
#set -o verbose
E_BADARGS=85
if [ ! -n "$1" ]
then
echo "Usage: `basename $0` grep -nrs --color -w --include=\"*.{h,c}\" Testing ."
echo "Usage: `basename $0` find . -name \"*.txt\""
exit $E_BADARGS
fi
# Create a temporary file
TMPFILE=$(mktemp)
# Add stuff to the temporary file
#echo "echo Hello World...." >> $TMPFILE
#initialize the variable that will contain the whole argument string
argList=""
#iterate on each argument
for arg in "$#"
do
#if an argument contains a white space, enclose it in double quotes and append to the list
#otherwise simply append the argument to the list
if echo $arg | grep -q " "; then
argList="$argList \"$arg\""
else
argList="$argList $arg"
fi
done
#remove a possible trailing space at the beginning of the list
argList=$(echo $argList | sed 's/^ *//')
# Echoing the command to be executed to tmp file
echo "$argList" >> $TMPFILE
# Note: This should be your last command
# Important last command which deletes the tmp file
last_command="rm -f $TMPFILE"
echo "$last_command" >> $TMPFILE
#echo "---------------------------------------------"
#echo "TMPFILE is $TMPFILE as follows"
#cat $TMPFILE
#echo "---------------------------------------------"
check_for_last_line=$(tail -n 1 $TMPFILE | grep -o "$last_command")
#echo $check_for_last_line
#if tail -n 1 $TMPFILE | grep -o "$last_command"
if [ "$check_for_last_line" == "$last_command" ]
then
#echo "Okay..."
bash $TMPFILE
exit 0
else
echo "Something is wrong"
echo "Last command in your tmp file should be removing itself"
echo "Aborting the process"
exit 1
fi
$ bash --init-file <(echo 'some_command')
$ bash --rcfile <(echo 'some_command')
In case you can't or don't want to use process substitution:
$ cat script
some_command
$ bash --init-file script
Another way:
$ bash -c 'some_command; exec bash'
$ sh -c 'some_command; exec sh'
sh-only way (dash, busybox):
$ ENV=script sh
Here is yet another (working) variant:
This opens a new gnome terminal, then in the new terminal it runs bash. The user's rc file is read first, then a command ls -la is sent for execution to the new shell before it turns interactive.
The last echo adds an extra newline that is needed to finish execution.
gnome-terminal -- bash -c 'bash --rcfile <( cat ~/.bashrc; echo ls -la ; echo)'
I also find it useful sometimes to decorate the terminal, e.g. with colorfor better orientation.
gnome-terminal --profile green -- bash -c 'bash --rcfile <( cat ~/.bashrc; echo ls -la ; echo)'

Resources