Set and execute a command from a function - fish

How can I write a fish function that executes a command in a string and make it appear in the history?
function qh --description 'Use peco to query command history'
if test (count $argv) = 0
set peco_flags --layout=bottom-up
else
set peco_flags --layout=bottom-up --query "$argv"
end
history | peco $peco_flags | read cmd
if test $cmd
commandline $cmd
else
commandline ''
end
end
This does not work...

It is possible to create a key binding which sets the command line and then executes it; that command will then appear in history. Example:
function whatday
commandline "echo Today is $(date +%A)"
commandline -f execute
end
bind \eq whatday
now alt-q will set the commandline to echo Today is Sunday and execute it; it appears in history.
Beyond that there are also abbreviations which allow replacing tokens with text; but the text is just static (e.g. gco -> git checkout).
There is as yet no way for an arbitrary fish function (e.g. run as part of a shell script) to append to history, only delete and read from it.

Related

How can I make a function run every time cd successfully changes to another directory within sh on FreeBSD?

I'm using sh as my shell on FreeBSD but I want to be able to have a pretty prompt like the one bash gives me on Ubuntu. There are two things that the FreeBSD implementation of sh seems to lack as far as PS1 escape characters go:
The \w works but does not expand $HOME to ~, so this is something I have already hacked up myself
I can use PS1 to update the prompt on the terminal, but as far as I can tell it is not possible to use the PS1 variable to update the title bar as well. ESC and BEL fail to set the title as one would expect if they were using bash or ksh
Here is my .shrc file
update_prompt() {
case "$PWD" in
"$HOME"*)
pretty_pwd="~${PWD#*"${HOME}"}"
;;
"/usr$HOME"*)
pretty_pwd="~${PWD#*"/usr${HOME}"}"
;;
*)
pretty_pwd="$PWD"
;;
esac
case "$TERM" in
xterm*|rxvt*)
PS1="[$USER#\\h $pretty_pwd]\\$ "
;;
*)
;;
esac
printf "\\033]0;[%s#$(hostname -s): %s]\\007" "$USER" "$pretty_pwd"
}
update_prompt
So when I fire up a terminal or log in via ssh, it gives the pretty prompt that I like. But now I need this function to run every time that cd is executed and returns an exit status of 0.
I was going to use an alias that was something like:
alias cd="cd $1 && update_prompt"
but that was before I realized that aliases do not except arguments. How might I go about doing something like this?
You can use a function instead of an alias:
cd() {
command cd "$#" && update_prompt
}
Just put it into ~/.shrc. You have to use command here to let sh know that you are referring to the actual cd builtin command instead of the function you've just defined.
Refer to the sh(1) manual page for the details on how to make sh(1) source the ~/.shrc file when it starts:
Therefore, a user should place commands that are to be executed only at login
time in the .profile file, and commands that are executed for every shell
inside the ENV file. The user can set the ENV variable to some file by placing
the following line in the file .profile in the home directory, substituting for
.shrc the filename desired:
ENV=$HOME/.shrc; export ENV
I use this trick in my cd alias manager. Here's a link to the source code of the function: https://github.com/0mp/goat/blob/v2.5.0/libgoat.sh#L31-L57
You can do it with alias+arguments if you swap the commands:
$ alias cd="echo change; cd"
$ pwd
/nas
$ cd /
change
$ pwd
/
$ cd /etc
change
$ pwd
/etc
$

How to expand environment variables using keyboard shortcut in Fish?

In Bash, the shortcut Esc Ctrl-e can be used to expand an environment variable at the shell:
$ echo $PATH
/home/joe
$ $PATH<Press Esc Ctrl-e>
$ /home/joe
Is there a shortcut to achieve something similar in Fish?
You could do something like this
function bind_expand_all
# what are the tokens of the current command line
set tokens (commandline --tokenize)
# erase the current command line (replace with empty string)
commandline -r ""
for token in $tokens
# append the expanded value of each token followed by a space
commandline -a (eval echo $token)" "
end
# move the cursor to the end of the new command line
commandline -C (string length (commandline))
end
then
bind \e\ce bind_expand_all
And if this is your current command line (with the cursor at the underscore):
$ echo $HOME (date -u)_
when you hit AltCtrle, you get
$ echo /home/jackman Thu May 10 19:27:18 UTC 2018 _
To store that binding permanently, add it to your fish_user_key_bindings function (create it if it does not exist):
Key bindings are not saved between sessions by default. Bare bind statements in config.fish won't have any effect because it is sourced before the default keybindings are setup. To save custom keybindings, put the bind statements into a function called fish_user_key_bindings, which will be autoloaded.
https://fishshell.com/docs/2.7/commands.html#bind
A little nicer:
function bind_expand_all
set -l expanded
for token in (commandline --tokenize)
set expanded $expanded (eval echo $token)
end
set -l new (string join " " $expanded)
commandline -r $new
commandline -C (string length $new)
end

Syntax error in script

My script try execute mvn clean install in all projects but before it tried switch to dev branch and pull it.
successString="[INFO] BUILD SUCCESS";
file="mvnoutput";
red=$'\e[1;31m';
grn=$'\e[1;32m';
end=$'\e[0m';
function checkResult
if grep -Fxq "$successString" $file
then
echo -en "${grn}[${1}]Build ok${end}";
else
echo "${red}[${1}]Error in mvn clean install${end}";
exit 1;
fi;
end
function pullAndSwitchDevBranch
git checkout dev;
git pull origin dev;
end
cd api-pay-commons/;
pullAndSwitchDevBranch;
touch mvnoutput;
mvn clean install -U > mvnoutput;
checkResult PAY;
Why I received this error?
line 17: end: command not found ./script.sh: line 20: syntax
error near unexpected token git' ./script.sh: line 20: git checkout dev;'
You have used the "fish" tag, so I'm assuming you are running this with the fish shell.
If so: This is not a valid fish script. Fish is explicitly not POSIX-compatible, so you might want to read up on the syntax. (If not, then please correct your tag)
red=$'\e[1;31m';
Fish does not use var=value to set a variable. It uses set var value.
Fish also does not have the $'' style of quoting. Instead, backslash-escapes are interpreted outside of quotes.
So this would be set red \e"[1;31m". Alternatively, fish offers the set_color builtin, which prints the escape sequence for a given color. So this could be set red (set_color red) (or you could call set_color later).
then
Fish does not use if condition; then dosomething; fi. It's if condition; dosomething; end.
echo -en "${grn}[${1}]Build ok${end}";
Fish does not use "${var}". It also does not call the function arguments $1 et al.
This would be something like echo -ens "$grn" "[$argv[1]]" "Build ok" "$end".
exit 1
Fish currently does not allow exiting from functions. You'll have to return 1 and check the result from the outside.
Additionally, you're using semicolons all over the place, which is not typical style in any shell I'm aware of.
In case you are not using fish, but some POSIX-compatible shell (like bash), this is also not valid. The main (and possibly only) issue is that you are using function something; dosomething; end. In bash, that would be
function something () {
#dostuff
}
And in pure POSIX, that would be
something () {
#dostuff
}

How to conditionally exit .BAT file from SQL code run through SQLCMD

I have a .bat (Windows command) file that includes invocations of SQLCMD and other commands. (Of course SQLCMD is sending my T-SQL code to SQL Server.) I want to detect certain conditions in the SQL code, and conditionally exit the entire batch file. I've tried various combinations of RAISERROR, THROW, and deliberate division by 0 (I'm not proud) along with various command line switches on SQLCMD and handling of errorlevel in the .bat file.
I tried the answer to 5789568 but could not get it to work in my case. Here are two files which show one failed attempt. It tries to abort if there are more than 3 tables. But it doesn't abort the bat file, as you can see when the final command (echo) executes. It doesn't even abort the run of SQLCMD, as you can see when it tells you how many tables there are.
example.bat
set ERRORLEVEL=0
sqlcmd -b -S dbread.vistaprint.net -E -d columbus -e -i example.sql
if %ERRORLEVEL% neq 0 exit /b %ERRORLEVEL%
echo we got to the end of the BAT file
example.sql
SET XACT_ABORT ON
if ((SELECT COUNT(*) FROM sys.tables) > 3)
begin
RAISERROR ('There are more than 3 tables. We will try to stop', 18, -1)
end
SELECT COUNT(*) FROM sys.tables
%ERRORLEVEL% is not a normal environment variable. It is a dynamic pseudo variable that returns the current ERRORLEVEL. If you explicitly define a true environment variable with set ERRORLEVEL=0, then the dynamic nature is destroyed, and %ERRORLEVEL% will forever return the value of your user defined environment variable until you undefine your user value. You should never define your own values for ERRORLEVEL, RANDOM, CD, TIME, DATE etc.
If you want to clear the ERRORLEVEL, then you must execute a command that sets the value to 0. I like to use (call ).
Assuming there are no other problems with your code, it should be fixed with the following:
(call )
sqlcmd -b -S dbread.vistaprint.net -E -d columbus -e -i example.sql
if %ERRORLEVEL% neq 0 exit /b %ERRORLEVEL%
echo we got to the end of the BAT file
sqlcmd is an external command (exe program), so it always sets ERRORLEVEL. Therefore you should not have to clear the ERRORLEVEL, and (call ) could be removed. You really only have to worry about clearing the ERRORLEVEL before you run internal commands that do not set ERRORLEVEL to 0 upon success. (See Which cmd.exe internal commands clear the ERRORLEVEL to 0 upon success?)
#dbenson's answer solves the problem with the .bat file. But there is also a problem with the .sql file, whose symptom is that the last line executes despite the error. Here is a complete solution. That is, these two files work correctly.
The "error" for which the code is checking is that a database named 'master' exists. If you replace 'master' with something that is not the name of a database, it runs without error.
example.bat
REM Replace the server name with any SQL Server server to which you
REM have at least read access. The contents of the server don't matter.
sqlcmd -b -S dbread.vistaprint.net -E -i example.sql
if %errorlevel% neq 0 exit /b %errorlevel%
echo 'In the .bat file, we got past the error check, so run was successful.'
example.sql
if exists (select * from master.sys.databases where name = 'master')
begin
RAISERROR ('Found an error condition', 18, 10)
return
end
print 'In the SQL file, we got past the error check, so run was successful.'

run Matlab in batch mode

It seems to me that there are two ways to run Matlab in batch mode:
the first one:
unset DISPLAY
matlab > matlab.out 2>&1 << EOF
plot(1:10)
print file
exit
EOF
The second one uses option "-r MATLAB_command":
matlab -nojvm -nosplash -r MyCommand
Are these two equivalent?
What does "<< EOF" and the last "EOF" mean in the first method?
Thanks and regards!
The first method simply redirects the standard output > matlab.out and the standard error 2>&1 to the file matlab.out.
Then it uses the heredoc way of passing input to MATLAB (this is not specific to MATLAB, it is a method of passing multiple lines as input to command line programs in general).
The syntax is << followed by an unique identifier, then your text, finally the unique id to finish.
You can try this on the shell:
cat << END
some
text
multiple lines
END
The second method of using the -r option starts MATLAB and execute the statement passed immediately. It could be some commands or the name of a script or function found on the path.
It is equivalent to doing something like:
python -c "print 'hello world'"
Refer to this page for a list of the other start options.