Store last command in a variable in Bash - bash

I'm want to store the most recent command in a variable. I tried the !!:p history expansion, it does get me the last command but I can't store it in a variable.
$ last=`!!:p`
last=`ls`
$ echo $last
$
Any help?

The fc command can be used to retrieve the previous command.
some_var=$(fc -nl -1)

Using !!:p only prints the last command, to execute the last command you would do !!.
$ mycmd="$(!!)"
$ sh "$mycmd"
That should do what you're looking for...

The solution given above does not work inside a script file:
some_var=$(fc -nl -1)
What I did was use trap option and store the last command in environment variable before the script is called and access the env variable in script.
Add this function in ~/.bashrc
function process_command() {
if [ "$BASH_COMMAND" != "$PROMPT_COMMAND" ];then
export myCMD=$BASH_COMMAND
fi
}
trap process_command DEBUG
Then use $myCMD variable in any script or anywhere in shell.

Related

Assigning a variable in a shell script for use outside of the script

I have a shell script that sets a variable. I can access it inside the script, but I can't outside of it. Is it possible to make the variable global?
Accessing the variable before it's created returns nothing, as expected:
$ echo $mac
$
Creating the script to create the variable:
#!/bin/bash
mac=$(cat \/sys\/class\/net\/eth0\/address)
echo $mac
exit 0
Running the script gives the current mac address, as expected:
$ ./mac.sh
12:34:56:ab:cd:ef
$
Accessing the variable after its created returns nothing, NOT expected:
$ echo $mac
$
Is there a way I can access this variable at the command line and in other scripts?
A child process can't affect the parent process like that.
You have to use the . (dot) command — or, if you like C shell notations, the source command — to read the script (hence . script or source script):
. ./mac.sh
source ./mac.sh
Or you generate the assignment on standard output and use eval $(script) to set the variable:
$ cat mac.sh
#!/bin/bash
echo mac=$(cat /sys/class/net/eth0/address)
$ bash mac.sh
mac=12:34:56:ab:cd:ef
$ eval $(bash mac.sh)
$ echo $mac
12:34:56:ab:cd:ef
$
Note that if you use no slashes in specifying the script for the dot or source command, then the shell searches for the script in the directories listed in $PATH. The script does not have to be executable; readable is sufficient (and being read-only is beneficial in that you can't run the script accidentally).
It's not clear what all the backslashes in the pathname were supposed to do other than confuse; they're unnecessary.
See ssh-agent for precedent in generating a script like that.

Expansion of variable does not work when calling bash functions

See also my previous question.
So... I have a script:
function go_loop (){
for i in `grep -v ^# $1`; do
$2
done
}
go_loop "/tmp/text.txt" "echo $i"
I should have in a result:
9
20
21
...
But apparently I only get an empty result. How can I feed the second input parameter to the loop?
Please don't advice me do this:
for i in `grep -v ^# $1`; do
echo $i
done
I need to make 2 input parameters, first - name of file, second - name of execution command
You need to eval the second parameter like this:
eval $2
and pass it like this:
go_loop "/tmp/text.txt" 'echo $i'
You can do this using exec inside your loop, which will run the $2 as bash command:
[root#box ~]# ./test.sh 1 ls
test.sh tests_passed.txt
[root#box ~]# cat test
exec $2
The exec builtin command is used to
replace the shell with a given program (executing it, not as new process)
set redirections for the program to execute or for the current shell

How to run "source" command (Linux) from a perl script?

I am trying to source a script from a Perl script (script.pl).
system ("source /some/generic/script");
Please note that this generic script could be a shell, python or any other script. Also, I cannot replicate the logic present inside this generic script into my Perl script. I tried replacing system with ``, exec, and qx//. Each time I got the following error:
Can't exec "source": No such file or directory at script.pl line 18.
I came across many forums on the internet, which discussed various reasons for this problem. But none of them provided a solution. Is there any way to run/execute source command from a Perl script?
In bash, etc, source is a builtin that means read this file, and interpret it locally (a little like a #include).
In this context that makes no sense - you either need to remove source from the command and have a shebang (#!) line at the start of the shell script that tells the system which shell to use to execute that script, or you need to explicitly tell system which shell to use, e.g.
system "/bin/sh", "/some/generic/script";
[with no comment about whether it's actually appropriate to use system in this case].
There are a few things going on here. First, a child process can't change the environment of its parent. That source would only last as long as its process is around.
Here's a short program that set and export an environment variable.
#!/bin/sh
echo "PID" $$
export HERE_I_AM="JH";
Running the file does not export the variable. The file runs in its own proces. The process IDs ($$) are different in set_stuff.sh and the shell:
$ chmod 755 set_stuff.sh
$ ./set_stuff.sh
PID 92799
$ echo $$
92077
$ echo $HERE_I_AM # empty
source is different. It reads the file and evaluates it in the shell. The process IDs are the same in set_stuff.sh and the shell, so the file is actually affecting its own process:
$ unset HERE_I_AM # start over
$ source set_stuff.sh
PID 92077
$ echo $$
92077
$ echo $HERE_I_AM
JH
Now on to Perl. Calling system creates a child process (there's an exec in there somewhere) so that's not going to affect the Perl process.
$ perl -lwe 'system( "source set_stuff.sh; echo \$HERE_I_AM" );
print "From Perl ($$): $ENV{HERE_I_AM}"'
PID 92989
JH
Use of uninitialized value in concatenation (.) or string at -e line 1.
From Perl (92988):
Curiously, this works even though your version doesn't. I think the different is that in this there are no special shell metacharacters here, so it tries to exec the program directory, skipping the shell it just used for my more complicated string:
$ perl -lwe 'system( "source set_stuff.sh" ); print $ENV{HERE_I_AM}'
Can't exec "source": No such file or directory at -e line 1.
Use of uninitialized value in print at -e line 1.
But, you don't want a single string in that case. The list form is more secure, but source isn't a file that anything can execute:
$ which source # nothing
$ perl -lwe 'system( "source", "set_stuff.sh" ); print "From Perl ($$): $ENV{HERE_I_AM}"'
Can't exec "source": No such file or directory at -e line 1.
Use of uninitialized value in concatenation (.) or string at -e line 1.
From Perl (93766):
That is, you can call source, but as something that invokes the shell.
Back to your problem. There are various ways to tackle this, but we need to get the output of the program. Instead of system, use backticks. That's a double-quoted context so I need to protect some literal $s that I want to pass as part of the shell commans
$ perl -lwe 'my $o = `echo \$\$ && source set_stuff.sh && echo \$HERE_I_AM`; print "$o\nFrom Perl ($$): $ENV{HERE_I_AM}"'
Use of uninitialized value in concatenation (.) or string at -e line 1.
93919
From Shell PID 93919
JH
From Perl (93918):
Inside the backticks, you get what you like. The shell program can see the variable. Once back in Perl, it can't. But, I have the output now. Let's get more fancy. Get rid of the PID stuff because I don't need to see that now:
#!/bin/sh
export HERE_I_AM="JH";
And the shell command creates some output that has the name and value:
$ perl -lwe 'my $o = `source set_stuff.sh && echo HERE_I_AM=\$HERE_I_AM`; print $o'
HERE_I_AM=JH
I can parse that output and set variables in Perl. Now Perl has imported part of the environment of the shell program:
$ perl -lwe 'my $o = `source set_stuff.sh && echo HERE_I_AM=\$HERE_I_AM`; for(split/\R/,$o){ my($k,$v)=split/=/; $ENV{$k}=$v }; print "From Perl: $ENV{HERE_I_AM}"'
From Perl: JH
Let's get the entire environment, though. env outputs every value in the way I just processed it:
$ perl -lwe 'my $o = `source set_stuff.sh && env | sort`; print $o'
...
DISPLAY=:0
EC2_PATH=/usr/local/ec2/ec2-api-tools
EDITOR=/usr/bin/vi
...
I have a few hundred varaibles set in the shell, and I don't want to expose most of them. Those are all set by the Perl process, so I can temporarily clear out %ENV:
$ perl -lwe 'local %ENV=(); my $o = `source set_stuff.sh && env | sort`; print $o'
HERE_I_AM=JH
PWD=/Users/brian/Desktop/test
SHLVL=1
_=/usr/bin/env
Put that together with the post processing code and you have a way to pass that information back up to the parent.
This is, by the way, similar to how you'd pass variables back up to a parent shell process. Since that output is already something the shell understands, you use the shell's eval instead of parsing it.
You can't. source is a shell function that 'imports' the contents of that script into your current environment. It's not an executable.
You can replicate some of it's functionality by rolling your own - run or parse whatever you're 'sourcing' and capture the result:
print `. file_to_source; echo $somevar`;
or similar.

How to get last command run without using `!!`?

I'm trying to alias _! to sudo the last command, but I'm running into roadblocks. !! doesn't seem to work in my .zshrc file, and sed has given me repeated problems. I tried using the following command, and several variations of it, but to no avail.
history | tail -1 | sed -e 's/[^0-9\*\ ]+/\0/g'
However, this still interpreted the piped input as a file, instead of a string of text. I also tried a variation using awk:
history | tail -1 | awk '{ gsub("/[^0-9\*\ ]+", "") ; system( "echo" $1 ) }'
I'm sure I'm just having some trouble putting the commands in correctly, but some help would be appreciated.
You can use the fc built-in to access the history programmatically. For example, I believe this will behave as you wish:
alias _!='fc -e "sed -i -e \"s/^/sudo /\""'
With no arguments, fc (short for "fix command") fires up $EDITOR on your previous command and runs the result of your editing. You can specify a different editor with the -e option; here, I'm specifying a non-interactive one in the form of a sed command that will insert sudo in front of the command line.
The command assumes GNU sed. As written, it will also work with the version that ships on modern BSD/macOS, but by way of a hackcident: it treats the -e as an argument to -i instead of a new option. Since the -e is optional with only one expression, this works fine, but it means that sed makes a backup of the temp file with -e on the end, which will hang around after the command completes. You can make that cleaner by using this alternative version on those systems:
alias _!='fc -e "sed -i \"\" -e \"s/^/sudo /\""'
(That won't work with GNU sed, which sees the empty string argument as a filename to operate on...)
On older systems, a more portable solution could use ed:
alias _!="fc -e 'ed -s <<<$'\''s/^/sudo /\nw\nq'\'"
You can often get away with something simpler, like sudo $(fc -ln -1) (-l = list commands, -n = without numbers, -1 = only the last command), but in general you will run into quoting issues, since the command is output by fc the way it was typed:
% touch '/etc/foo bar'
touch: /etc/foo bar: Permission denied
% sudo $(fc -ln -1)
touch: '/etc/foo: No such file or directory
None of the above is limited to zsh, btw; fc dates to the original version of ksh, so it will also work in bash, etc.
This fc command will always give most recently executed command in zsh and in bash:
fc -ln -1
As per help fc:
-l (letter el) list lines instead of editing
-n omit line numbers when listing
-1 (minus one) gets the just executed command.
Found a amazing widget to sudo:
sudo-command-line() {
[[ -z $BUFFER ]] && zle up-history
[[ $BUFFER != sudo\ * ]] && {
typeset -a bufs
bufs=(${(z)BUFFER})
if (( $+aliases[$bufs[1]] )); then
bufs[1]=$aliases[$bufs[1]]
fi
bufs=(sudo $bufs)
BUFFER=$bufs
}
zle end-of-line
}
zle -N sudo-command-line
bindkey "\e\e" sudo-command-line
Author:lilydjwg
The following is the way to run the last command in command:
fc -ln -1 is the simplest way, but one problem, when run something with some spaces at the beginning of the command, this command won't shown up in history, anything based on history won't work properly.
So we need ZLE(Zsh Line Editor) to store the command manually.
Store_Your_Command () {
if [[ -z $BUFFER ]]
then
# If nothing input, just clear the screen
zle clear-screen
else
zle accept-line
# Remember the last command, useful in some alias
# Add space at the beginning of a command, this command wont
# show up in history, so use variables to store the command
LAST_COMMAND=$CURRENT_COMMAND
CURRENT_COMMAND=$BUFFER
fi
}
# Create a user-defined widget
zle -N Store_Your_Command
# Bind it to the **Enter** key
bindkey "^M" Store_Your_Command
Then whenever we press enter to run a command, this command will be stored in $CURRENT_COMMAND, and the last command will be stored in $LAST_COMMAND.
Want to run the last command? Just run eval $LAST_COMMAND, you can also put it to your alias.
When some alias in the last command, zsh wont run the last command correctly, so we need to expand our alias: when we input an alias, replace the alias to the original command/content, with help of the builtin zle: _expand_alias.
First, delete the widget we just added.
Add those to your .zshrc:
# When input space, expand alias -----------------------------------{{{
expand_alias_space () {
zle _expand_alias
zle self-insert
}
zle -N expand_alias_space
bindkey " " expand_alias_space
# }}}
# When input enter, expand alias -----------------------------------{{{
expand_alias_enter () {
if [[ -z $BUFFER ]]
then
zle clear-screen
else
zle _expand_alias
zle accept-line
# Remember the last command, useful in some alias
# Add space at the beginning of a command, this command won't
# show up in history, so use variables to store the command
LAST_COMMAND=$CURRENT_COMMAND
CURRENT_COMMAND=$BUFFER
fi
}
zle -N expand_alias_enter
bindkey "^M" expand_alias_enter
# }}}
Now we can expand alias to the original command/content by press Space key or just press Enter key to run the command, and use eval $LAST_COMMAND to run the last command without any problems.
But it will call another problem when run a command use eval $LAST_COMMAND twice:
zsh: job table full or recursion limit exceeded
We need to replace eval $LAST_COMMAND to the real command, because $LAST_COMMAND always change.
We write a function run the last command like this
# Echo the last command
fun()
{
# The command we need to run in this function
CURRENT_COMMAND="echo \[`echo $LAST_COMMAND`\]"
# run the command
eval $CURRENT_COMMAND
}
The command stored in $CURRENT_COMMAND wont change like eval $LAST_COMMAND does.
problem sloved.
No more problem I hope
If I want . to be the alias for last command,
lastcmd() {
# start climbing back in history, checking for alias
n=-1
lc=$(fc -ln $n $n)
# "." is checked because I have aliased lastcmd to "."
while [ "$lc" = "lastcmd" ] || [ "$lc" = "." ]
do
n=$(( $n - 1 ))
lc=$(fc -ln $n $n)
done
eval ${lc}
}
alias .=lastcmd
This is pretty simple and works well for me. Replace the second condition in while with whatever alias you end up using.
Add this to your .zshrc --
func preexec() {
export LAST_COMMAND="$1"
}

Passing a variable into awk within a shell script

I have a shell script that I'm writing to search for a process by name and return output if that process is over a given value.
I'm working on finding the named process first. The script currently looks like this:
#!/bin/bash
findProcessName=$1
findCpuMax=$2
#echo "parameter 1: $findProcessName, parameter2: $findCpuMax"
tempFile=`mktemp /tmp/processsearch.XXXXXX`
#echo "tempDir: $tempFile"
processSnapshot=`ps aux > $tempFile`
findProcess=`awk -v pname="$findProcessName" '/pname/' $tempFile`
echo "process line: "$findProcess
`rm $tempFile`
The error is occuring when I try to pass the variable into the awk command. I checked my version of awk and it definitely does support the -v flag.
If I replace the '/pname/' portion of the findProcess variable assignment the script works.
I checked my syntax and it looks right. Could anyone point out where I'm going wrong?
The processSnapshot will always be empty: the ps output is going to the file
when you pass the pattern as a variable, use the pattern match operator:
findProcess=$( awk -v pname="$findProcessName" '$0 ~ pname' $tempFile )
only use backticks when you need the output of a command. This
`rm $tempFile`
executes the rm command, returns the output back to the shell and, it the output is non-empty, the shell attempts to execute that output as a command.
$ `echo foo`
bash: foo: command not found
$ `echo whoami`
jackman
Remove the backticks.
Of course, you don't need the temp file at all:
pgrep -fl $findProcessName

Resources