How to remove functions from fishshell without deleting the function file directly? - function

I've defined a function hello in fishshell:
function hello
echo Hello
end
And save it:
funcsave hello
If I want to delete it, I can delete the file ~/.config/fish/functions/hello.fish.
Is there any other way to do it? (like built-in funcdel or funcrm)

No, there isn't any builtin to remove the file, but you can use:
functions --erase hello
or
functions -e hello
to erase the function definition from the current session.
See also
Documentation

I created another fish function for that
function funcdel
if test -e ~/.config/fish/functions/$argv[1].fish
rm ~/.config/fish/functions/$argv[1].fish
echo 'Deleted function ' $argv[1]
else
echo 'Not found function ' $argv[1]
end
end

The above solution of functions -e hello only deletes hello in the current session. Open another terminal, and the function is still there.
To delete the function in a persistent way, I had to resort to deleting the file ~/.config/fish/functions/hello.fish directly. Up till now, I do not know of another way that does deleting in a persistent way.

a more complete, self defined (and quiet) self-crafted solution, inspired by #Kanzee's answer (copy to file ~/.config/fish/functions/funcdel.fish):
function funcdel --description 'Deletes a fish function both permanently and from memory'
set -l fun_name $argv[1]
set -l fun_file ~/.config/fish/functions/$fun_name.fish
# Delete the in-memory function, if it exists
functions --erase $fun_name
# Delete the function permanently,
# if it exists as a file in the regular location
if test -e $fun_file
rm $fun_file
end
end

I combined the answer from #hoijui with some code from the function funcsave, so you can delete more than one function at once:
function funcdel --description 'Deletes a fish function both permanently and from memory'
set cf (status function)
set -l options 'h/help'
argparse -n funcdel $options -- $argv
or return
# should create a manpage
if set -q _flag_help
__fish_print_help cf
return 0
end
if not set -q argv[1]
printf (_ "%ls: Expected at least %d args, got only %d\n") $cf 1 0
return 1
end
set -l retval 0
for funcname in $argv
set -l funcfile $__fish_config_dir/functions/$funcname.fish
# Delete the in-memory function, if it exists
functions --query -- $funcname
if test $status -eq 0
functions --erase -- $funcname
printf (_ "%s: function %s removed from session\n") $cf $funcname
else
printf (_ "%s: Unknown function '%s'\n") $cf $funcname
end
# Delete the function permanently,
# if it exists as a file in the regular location
if test -e $funcfile
rm $funcfile
printf (_ "%s: %s deleted\n") $cf $funcfile
else
printf (_ "%s: %s not found\n") $cf $funcfile
end
end
return $retval
end

Related

widgets can only be called when ZLE is active

I have been dealing with this problem for almost a month now, and I feel frustrated, Any help would be greatly appreciated.
I am trying to write a widget for my takenote command. The purpose of the widget is to feed all the markdown files in ~/notes folder into fzf so that the user can select one of them and starts editing it.
After the user types takenote and presses <tab> I expect the widget to run.
Here is the _takenote.zsh widget definition:
#compdef takenote
local file=$( find -L "$HOME/notes/" -print 2> /dev/null | fzf-tmux +m )
zle reset-prompt
compadd $file
return 1
Unfortunately, the above code doesn't work because of zle reset-prompt, if I remove it then the result would be like this:
And after selecting the file it would turn into:
Which as you see will corrupt the prompt and the command itself.
It appears to me that what I need to do is do a zle reset-prompt
before calling compadd but this can only work when I bind the function to a key otherwise, I will get the following error:
widgets can only be called when ZLE is active
I finally found a workaround for the issue. Although I am not satisfied with the strategy since it is not self contained in the widget itself, but it works. The solution involves trapping fzf-completion after it is invoked and calling zle reset-prompt.
For registering the trap add the following snippet to your .zshrc file (see Zsh menu completion causes problems after zle reset-prompt
):
TMOUT=1
TRAPALRM() {
if [[ "$WIDGET" =~ ^(complete-word|fzf-completion)$ ]]; then
# limit the reset-prompt functionality to the `takenote` script
if [[ "$LBUFFER" == "takenote "* ]]; then
zle reset-prompt
fi
fi
}
The _takenote widget:
#compdef takenote
local file=$( find -L "$HOME/notes/" -print 2> /dev/null | fzf-tmux +m )
compadd $file
return 0
p.s: I would still love to move the trap inside the widget, and avoid registering it in the init script (.zshrc)
After two days, I finally managed to find a hint on how to achieve it thanks to the excellent fzf-tab-completion project:
https://github.com/lincheney/fzf-tab-completion/blob/c91959d81320935ae88c090fedde8dcf1ca70a6f/zsh/fzf-zsh-completion.sh#L120
So actually, all that you need to do is:
#compdef takenote
local file=$( find -L "$HOME/notes/" -print 2> /dev/null | fzf-tmux +m )
compadd $file
TRAPEXIT() {
zle reset-prompt
}
return 0
And it finally works. Cheers!
I was getting the same error when trying to use bindkey for a widget to use vim to open the fzf selected file. Turns out I have to open the file in function1 and then have a function2 calling function1 and then reset-prompt to avoid this widgets can only be called when ZLE is active error. Like you said, it is really frustrating and took me almost a day to figure out!
Example code:
## use rg to get file list
export FZF_DEFAULT_COMMAND='rg --files --hidden'
## file open (function1)
__my-fo() (
setopt localoptions pipefail no_aliases 2> /dev/null
local file=$(eval "${FZF_DEFAULT_COMMAND}" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS --preview 'bat --color=always --line-range :500 {}'" $(__fzfcmd) -m "$#" | while read item; do
echo -n "${(q)item}"
done)
local ret=$?
if [[ -n $file ]]; then
$EDITOR $file
fi
return $ret
)
## define zsh widget(function2)
__my-fo-widget(){
__my-fo
local ret=$?
zle reset-prompt
return $ret
}
zle -N __my-fo-widget
bindkey ^p __my-fo-widget

Passing arguments as strings to a function in a shell script

Calling a function in another script to delete old files - need to pass $1 as string, and not the eval of that arg (filelist from directory)
Have tried:
- single and double quotes around echo $1 ("$1", '$1')
- single and double quotes around arg ("/tmp/AB*", '/tmp/AB*')
Have read 3 similar questions here, but unsuccessful at understanding the issue...
AIX 6
#!/bin/ksh
#### common load function ######
. /tmp/functions.sh
deletefiles /usr/tmp/AB* 1
#!/bin/sh
# Deletes files from a filelist that are older than X days
deletefiles() {
echo $1
echo $2
#filelist=$1
#days=$2
#execute
#`find ${filelist} -type f -mtime +${days} -exec rm {} + 2>&1`
}
It looks like you want to pass /usr/tmp/AB* as is, without expanding it. This can be done with '/usr/tmp/AB*', "/usr/tmp/AB*", or /usr/tmp/AB\*.
Then, to confirm that you got the right value, you need to use "$1" to prevent wildcard expansion in echo:
deletefiles() {
echo "$1"
echo "$2"
}
deletefiles '/usr/tmp/AB*' 1
I guess your main problem is that you want AB* expanded in deletefiles().
When you don't do something special, how do you find the last parameter?
You can expand the wildcard within deletefiles() with eval, but eval can do more than you wanted. Another method is swithing the order of your parameters (days first) end use shift for deleting days from the paramaterlist when you assigned it to a var.
I'll show both solutions.
deletefiles_notsecure() {
filelist="$(eval echo $1)"
days=$2
echo "Filelist: $filelist"
echo "Days: $days"
}
deletefiles_secure() {
days=$1
shift
filelist="$*"
echo "Filelist: $filelist"
echo "Days: $days"
}
# deletefiles /usr/tmp/AB* 1
deletefiles_notsecure "/tmp/*" 1
echo ===========
deletefiles_secure 1 /tmp/*
As you can see, the second form can be used without quotes from the caller, so that will be easier to use.
Note: It will expand the vars during the call, relative to path you are standing in. When deletefiles_secure() starts with cd "${logdir}" and you are standing in your $HOME when you call deletefiles_secure 1 access*.log* an access.log in your homedir will be found. Use full paths on your local computer!
Don't use eval if you can avoid it: find does have a -name option to specify a file-mask, eg:
deletefiles () {
find "$1" -name "$2" ...
}
deletefiles /somedir 'AB*'

unix function return if any error occurs

I have a unix script in which I am calling functions.
I want the function should return immediately if any of the command failed in between.
But checking $? after every command I can not do. Is there any other way to do this.
Maybe running the script from a file line by line (as long of course as each of your functions are one line long).
Maybe the following script can be a starting point:
#!/bin/sh
while read l
do
eval "$l || break"
done <<EOF
echo test | grep e
echo test2 | grep r
echo test3 grep 3
EOF
This is another idea after my previous answer. It works with bash script and requires your functions to be quite simple (pipes may cause some issues):
#!/bin/bash
set -o monitor
check() {
[ $? -eq 0 ] && exit
}
trap check SIGCHLD
/bin/echo $(( 1+1 ))
/bin/echo $(( 1/0 ))
/bin/echo $(( 2+2 ))
Furthermore: functions need to be external command (this is why I use /bin/echo rather than echo). Regards.

How to parse json response in the shell script?

I am working with bash shell script. I need to execute an URL using shell script and then parse the json data coming from it.
This is my URL - http://localhost:8080/test_beat and the responses I can get after hitting the URL will be from either these two -
{"error": "error_message"}
{"success": "success_message"}
Below is my shell script which executes the URL using wget.
#!/bin/bash
DATA=$(wget -O - -q -t 1 http://localhost:8080/test_beat)
#grep $DATA for error and success key
Now I am not sure how to parse json response in $DATA and see whether the key is success or error. If the key is success, then I will print a message "success" and print $DATA value and exit out of the shell script with zero status code but if the key is error, then I will print "error" and print $DATA value and exit out of the shell script with non zero status code.
How can I parse json response and extract the key from it in shell script?
I don't want to install any library to do this since my JSON response is fixed and it will always be same as shown above so any simpler way is fine.
Update:-
Below is my final shell script -
#!/bin/bash
DATA=$(wget -O - -q -t 1 http://localhost:8080/tester)
echo $DATA
#grep $DATA for error and success key
IFS=\" read __ KEY __ MESSAGE __ <<< "$DATA"
case "$KEY" in
success)
exit 0
;;
error)
exit 1
;;
esac
Does this looks right?
If you are going to be using any more complicated json from the shell and you can install additional software, jq is going to be your friend.
So, for example, if you want to just extract the error message if present, then you can do this:
$ echo '{"error": "Some Error"}' | jq ".error"
"Some Error"
If you try this on the success case, it will do:
$echo '{"success": "Yay"}' | jq ".error"
null
The main advantage of the tool is simply that it fully understands json. So, no need for concern over corner cases and whatnot.
#!/bin/bash
IFS= read -d '' DATA < temp.txt ## Imitates your DATA=$(wget ...). Just replace it.
while IFS=\" read -ra LINE; do
case "${LINE[1]}" in
error)
# ERROR_MSG=${LINE[3]}
printf -v ERROR_MSG '%b' "${LINE[3]}"
;;
success)
# SUCCESS_MSG=${LINE[3]}
printf -v SUCCESS_MSG '%b' "${LINE[3]}"
;;
esac
done <<< "$DATA"
echo "$ERROR_MSG|$SUCCESS_MSG" ## Shows: error_message|success_message
* %b expands backslash escape sequences in the corresponding argument.
Update as I didn't really get the question at first. It should simply be:
IFS=\" read __ KEY __ MESSAGE __ <<< "$DATA"
[[ $KEY == success ]] ## Gives $? = 0 if true or else 1 if false.
And you can examine it further:
case "$KEY" in
success)
echo "Success message: $MESSAGE"
exit 0
;;
error)
echo "Error message: $MESSAGE"
exit 1
;;
esac
Of course similar obvious tests can be done with it:
if [[ $KEY == success ]]; then
echo "It was successful."
else
echo "It wasn't."
fi
From your last comment it can be simply done as
IFS=\" read __ KEY __ MESSAGE __ <<< "$DATA"
echo "$DATA" ## Your really need to show $DATA and not $MESSAGE right?
[[ $KEY == success ]]
exit ## Exits with code based from current $?. Not necessary if you're on the last line of the script.
You probably already have python installed, which has json parsing in the standard library. Python is not a great language for one-liners in shell scripts, but here is one way to use it:
#!/bin/bash
DATA=$(wget -O - -q -t 1 http://localhost:8080/test_beat)
if python -c '
import json, sys
exit(1 if "error" in json.loads(sys.stdin.read()) else 0)' <<<"$DATA"
then
echo "SUCCESS: $DATA"
else
echo "ERROR: $DATA"
exit 1
fi
Given:
that you don't want to use JSON libraries.
and that the response you're parsing is simple and the only thing you care about is the presence of substring "success", I suggest the following simplification:
#!/bin/bash
wget -O - -q -t 1 http://localhost:8080/tester | grep -F -q '"success"'
exit $?
-F tells grep to search for a fixed (literal) string.
-q tells grep to produce no output and instead only reflect via its exit code whether a match was found or not.
exit $? simply exits with grep's exit code ($? is a special variable that reflects the most recently executed command's exit code).
Note that if you all you care about is whether wget's output contains "success", the above pipeline will do - no need to capture wget's output in an aux. variable.

Execute commands using function calls in shell scripts

I am trying to execute a command using function calls in a shell script. When I pass the command to that function as an argument of that function, it does not work.
Function definition:
function ExecuteCommand() (
# $1: User#Host
# $2: Password
# $3: Command to execute
# Collect current IFS value
OLD_IFS=$IFS
# Set IFS value to new line feed
IFS=$'\n'
#Execute the command and capture the output
EXPECT_OUTPUT=($(expect ssh_exec.expect $1 $2 $3))
#Print the output
OUTPUT_LINE_COUNT=${#EXPECT_OUTPUT[#]}
for ((OUTPUT_LINE_INDEX=0; OUTPUT_LINE_INDEX<OUTPUT_LINE_COUNT; OUTPUT_LINE_INDEX++)) ;
do
echo ${EXPECT_OUTPUT[$OUTPUT_LINE_INDEX]}
done
# Get back to the original IFS
IFS=$OLD_IFS
)
Function call:
ExecuteCommand oracle#192.168.***.*** password123 "srvctl status database -d mydb"
And the output I get is:
spawn ssh oracle#192.168.***.*** {srvctl status database -d mydb}
oracle#192.168.***.***'s password:
bash: {srvctl: command not found
But when I don't pass the command as an argument of the function, it works perfectly:
Function definition in that case:
function ExecuteCommand() (
# $1: User#Host
# $2: Password
# Collect current IFS value
OLD_IFS=$IFS
# Set IFS value to new line feed
IFS=$'\n'
#Execute the command and capture the output
EXPECT_OUTPUT=($(expect ssh_exec.expect $1 $2 srvctl status database -d mydb))
#Print the output
OUTPUT_LINE_COUNT=${#EXPECT_OUTPUT[#]}
for ((OUTPUT_LINE_INDEX=0; OUTPUT_LINE_INDEX<OUTPUT_LINE_COUNT; OUTPUT_LINE_INDEX++)) ;
do
echo ${EXPECT_OUTPUT[$OUTPUT_LINE_INDEX]}
done
# Get back to the original IFS
IFS=$OLD_IFS
)
Function call:
ExecuteCommand oracle#192.168.***.*** password123
And I get the output just as I expected:
spawn ssh oracle#192.168.***.*** srvctl status database -d mydb
oracle#192.168.***.***'s password:
Instance mydb1 is running on node mydb1
Instance mydb2 is running on node mydb2
Instance mydb3 is running on node mydb3
Please help me about what was wrong while passing the command as a function parameter here in the first case.
If I am not wrong, any argument to "expect" within double quotes gets replaced with curly braces. Hence, the expect command became like:
expect ssh_exec.expect oracle#192.168.***.*** {srvctl status database -d mydb}
which made the shell to interpret "{srvctl" as a command.
Try using it like this:
EXPECT_OUTPUT=($(expect ssh_exec.expect $*))
instead of
EXPECT_OUTPUT=($(expect ssh_exec.expect $1 $2 $3))
And call your function like:
ExecuteCommand oracle#192.168.***.*** password123 srvctl status database -d mydb