Fish shell wildcard returns different output - fish

I'm trying to create a fish function with named argument contains a wildcard *. But the output between the function I made and the plain command are different.
Here's my function:
function ls-wildcard -a arg
ls $arg
end
and here's the result when I tried to execute it
$ ls-wildcard path/*.foo
bar1.foo
The output shows only 1 file when there should be 2 of them. But the plain ls works like a charm.
$ ls path/*.foo
bar1.foo bar2.foo
Am I missing something?
Edit:
After I tried some different expressions, the behavior of the function seems to terminate itself after the first matched. any way to fix it?

The wildcard is expanded before the function is run.
Your function uses a named argument, which is one argument.
So it is equivalent to this:
function ls-wildcard
ls $argv[1]
end
ls-wildcard path/*.foo
# runs `ls-wildcard` like
ls-wildcard path/bar1.foo path/bar2.foo
and then your function throws away the second argument.
The simplest fix is to just use $argv:
function ls-wildcard
ls $argv
end
which will forward all arguments to ls.

Related

Fish how to prevent recursive function calls

How can I prevent recursive function calls on fish functions when overriding a default binary with a function that has the same name ?
eg.
# Override 'ls'
function ls
if [ my_special_condition ]
* Do special stuff *
else # Call regular ls
ls $argv
end
end
Obsiously the above code ends up in a resursive loop without calling the actual 'ls' binary.
Is there a way to fix this ?
Within the function, use the command command
function ls
command ls $argv
end
I understand that you want to replace the ls function while also being able to call the original. You can do that by copying the function via functions -c:
functions -c ls orig_ls # copies ls to orig_ls
function ls
if [ my_special_condition ]
* Do special stuff *
else # Call original ls
orig_ls $argv
end
end

How can I enable tab-completion for `#` path options to HTTPie in fish?

HTTPie accepts paths as arguments with options that include the # sign. Unfortunately, they don't seem to work with shell completions in fish. Instead, the option is treated as an opaque string.
To stick with the file upload example from the HTTPie documentation with a file at ~/files/data.xml, I would expect to be able to tab complete the file name when typing:
http -f POST pie.dev/post name='John Smith' cv#~/files/da<TAB>
However, no completion is offered.
I have installed the completions for fish from the HTTPie project and they work for short and long arguments. This file does not specify how to complete the # arguments though.
In addition, I looked into specifying my own completions but I am not able to find a way of getting to work file completions with the arbitrary prefix.
How could I implement a completion for these path arguments for HTTPie?
Currently, the fish completions for HTTPie do not have completion for file path arguments with #. There is a more general GitHub Issue open about this.
If this is something you'd like to work on, either for yourself or for the project, you might be able draw some inspiration for the fish implementation from an HTTPie plugin for zsh+ohmyzsh that achieves your desired behaviour.
I managed to get the tab completion of the path arguments working with some caveats.
This adds the completion:
complete -c http --condition "__is_httpie_path_argument" -a "(__complete_httpie_path_argument (commandline -t))"
With the following functions:
function __is_httpie_path_argument
set -l arg (commandline -t)
__match_httpie_path_argument --quiet -- $arg
end
function __match_httpie_path_argument
string match --entire --regex '^([^#:=]*)(#|=#|:=#)(.*)$' $argv
end
function __complete_httpie_path_argument
__complete_httpie_path_argument_helper (__match_httpie_path_argument -- $argv[1])
end
function __complete_httpie_path_argument_helper
set -l arg $argv[1]
set -l field $argv[2]
set -l operator $argv[3]
set -l path $argv[4]
string collect $field$operator(__fish_complete_path $path)
end
The caveat is that this does not expand any variables nor the tilde ~. It essentially only works for plain paths — relative or absolute.

/bin/dash: strange behavior when matching wildcard *

I want to iterate over all files in given directory. This is my script (simplified):
#!/bin/sh
for F in /tmp/ZZZ/* ; do
echo $F
done
This works as expected, when directory /tmp/ZZZ/ contains files:
$ ./myscript.sh
/tmp/ZZZ/aaa
But the problem is, when directory is empty, instead of not echoing anything, the script echos the literal *:
$ ./myscript.sh
/tmp/ZZZ/*
Why is this happening?
Is there any way change this behavior of the shell ?

How can I ensure my autocompleted spaces are fed into my function properly?

I'm using zsh, and am trying to write a function to operate on a URL and a pathname:
function my-function
{
somecommand --url $1 $(readlink -f $2)
}
(to complicate things somewhat, the function actually uses sh syntax, as it is sourced from my ~/.zshrc using a trick like this). The readlink is there to expand symlinks and ensure directories such as . are evaluated correctly (the directory name is stored for later use by somecommand).
When I type a command from the command-line like this:
my-function http://example.org/example /tmp/myexampledirectory
... it works fine, even if I autocomplete the directory name. However, if the directory name contains spaces, zsh completes it like this:
my-function http://example.org/example /tmp/My\ Example\ Directory
For most "normal" commands (cp, mv, etc.) that never seems to cause a problem. However, in my case, somecommand sees $2 as only being /tmp/My - presumably the rest is seen as another argument.
How can I avoid this situation? I would prefer not to alter the standard zsh autocompletion, but rather find a way for my function to handle this.
The zsh completion system works very well here, and the solution is very simple, just put double-quotes around the readlink argument in the script:
somecommand --url $1 $(readlink -f "$2")
The point is that without quotes readlink removes backslashes which escape whitespaces. Compare three results:
1. Without backslashes and quotes readlink -f assumes that there are three different files/directories (with default path in current directory) and produces
$ readlink -f /tmp/My Example Directory
/tmp/My
/home/jimmij/Example
/home/jimmij/Directory
2. With escaping backslashes but without quotes readlink -f understands that there is only one directory, but removes backslashes from output, so that somecommand takes three separate arguments
$ readlink -f /tmp/My\ Example\ Directory
/tmp/My Example Directory
3. With backslashes and with double-quotes readlink -f gives the output with backslashes what is (most probably) expected by somecommand
$ readlink -f "/tmp/My\ Example\ Directory"
/tmp/My\ Example\ Directory
BTW, as a rule of thumb: if there are any problems with whitespaces in the shell-like scripts (bash, zsh, whatever) the first thing to play with is different quotation marks around variables.

How to force bash/zsh to evaluate parameter as multiple arguments when applied to a command

I am trying to run a program like this:
$CMD $ARGS
where $ARGS is a set of arguments with spaces. However, zsh appears to be handing off the contents of $ARGS as a single argument to the executable. Here is a specific example:
$export ARGS="localhost -p22"
$ssh $ARGS
ssh: Could not resolve hostname localhost -p22: Name or service not known
Is there a bash or zsh flag that controls this behavior?
Note that when I put this type of command in a $!/bin/sh script, it performs as expected.
Thanks,
SetJmp
In zsh it's easy:
Use cmd ${=ARGS} to split $ARGS into separate arguments by word (split on whitespace).
Use cmd ${(f)ARGS} to split $ARGS into separate arguments by line (split on newlines but not spaces/tabs).
As others have mentioned, setting SH_WORD_SPLIT makes word splitting happen by default, but before you do that in zsh, cf. http://zsh.sourceforge.net/FAQ/zshfaq03.html for an explanation as to why whitespace splitting is not enabled by default.
If you want to have a string variable (there are also arrays) be split into words before passing to a command, use $=VAR. There is also an option (shwordsplit if I am not mistaking) that will make any command $VAR act like command $=VAR, but I suggest not to set it: I find it very inconvenient to type things like command "$VAR" (zsh: command $VAR) and command "${ARRAY[#]}" (zsh: command $ARRAY).
It will work if you use eval $CMD $ARGS.