how to use optional flag in fish - fish

I'm building a CLI tasks utility, (A cheap version of taskwarrior).
I want to add some optional flags, such as -n
else if [ $cmd = 'delete' ]
argparse 'n/index'=? -- $argv
sed -i "$_flag_index d" ~/.tasks/data/Tdo.csv
but this gives an error
~/.tasks/run.sh (line 14): No matches for wildcard “'n/index'=?”. See help expand.
argparse 'n/index'=? -- $argv
I'm unable to understand the correct usage of optional flags, and I've not been able to find enough resources, the fish shell documentation isn't sufficient for a novice in shell scripting given lack of examples.
How to accept an optional argument n/index, and further execute some code if the argument has been given, and something else otherwise, and is it possible to add integer constraints on optional arguments?

Did you help expand like fish told you?
The unquoted ? is being handled as a globbing character. Use 'n/index=?'
$ set argv --index=10
$ argparse 'n/index'=? -- $argv
fish: No matches for wildcard ''n/index'=?'. See `help expand`.
argparse --ignore-unknown 'n/index'=? -- $argv
^
# quote the whole thing
# v v
$ argparse 'n/index=?' -- $argv
$ set -S _flag_index
$_flag_index: set in local scope, unexported, with 1 elements
$_flag_index[1]: |10|

Related

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.

Package Installation through script

I do understand the following script is installing the packages but what I don't understand is what packages:
for package in ${d[#]};
do
rpm -ivh --quiet ${!package} >/dev/null 2>&1
What is ${d[#]} ?
In this case 'd' is a name of an array. the '#' in the square brackets means 'each element of the array individually'.
So 'for' loops over each package listed in the array.
About the ${!package} (taken from the bash manual man bash section 'EXPANSION'):
If the first character of parameter is an exclamation point (!), a
level of variable indirection is introduced. Bash uses the value of
the variable formed from the rest of parameter as the name of the
variable; this variable is then expanded and that value is used in the
rest of the substitution, rather than the value of parameter itself.
This is known as indirect expansion.
As you did not post the full script i cannot really see how this works.

Can you `split` fish shell variables as cmd line args

Is it possible to have fishshell split variables that are in cmd line arguments?
Assume I have a variable $args set like so:
$ set args "-a args"
Now, given this python program (test.py):
import sys
print(sys.argv)
If I run the above in fishshell I get this output:
$ python test.py $args
['test.py', '-a args']
Notice that the arguments are passed as one argument. When I do the equivalent in bash I get this output:
$ python test.py $args
['test.py', '-a', 'params']
Is there someway to make fish behave like bash?
You do not want fish to behave like bash (technically any POSIX compatible shell) with respect to variable expansion. The POSIX behavior is the source of endless problems and is why you need to put double-quotes around almost everything. In fact, most experienced people will tell you to add IFS=$'\n' at the top of your scripts to stop that auto-splitting from happening.
One answer is to use fish's "every var is a list" feature: set args "-a" "args" (the quotes are just for clarity and aren't needed in this example). Each element of the list becomes a separate argument to the command. This will do the right thing even if the args value contains whitespace. The other answer is to explicitly split the string on whitespace using command substitution: a_cmd (string split ' ' $args). This will not do the right thing (in fish or bash) if the args value contains whitespace.
I found a little hack with fish commandline tokenization:
function posix_expand_str --description "Expand a string the POSIX way."
set __posix_expand_str__oldline (commandline)
commandline $argv
commandline -o
commandline $__posix_expand_str__oldline
set -e __posix_expand_str__oldline
end
All strings seem like they were concatenated during testing.
When you realize this answered your question, please accept. It only POSIXes when you ask it to, and does not break strings.
Test results:
> posix_expand_str "hello world"
hello
world
> posix_expand_str "hello 'posix haters' world"
hello
posix haters
world
> posix_expand_str "hello" 'high rep "stackoverflow staff"' "world"
hello
high
rep
stackoverflow staff
world

Checking if a file is a text file without using -T?

Title is pretty self explanatory, are there file testing functions in perl or is there a built in module that allows file testing operations?
This is a non-issue as -T like all of the file test operators are perl builtins.
They are documented here: perldoc -X
-X FILEHANDLE
-X EXPR
-X DIRHANDLE
-X
A file test, where X is one of the letters listed below. This unary operator takes one argument, either a filename, a filehandle, or a dirhandle, and tests the associated file to see if something is true about it. If the argument is omitted, tests $_ , except for -t , which tests STDIN. Unless otherwise documented, it returns 1 for true and '' for false, or the undefined value if the file doesn't exist. Despite the funny names, precedence is the same as any other named unary operator.
...
-T File is an ASCII text file (heuristic guess).
-B File is a "binary" file (opposite of -T).
The "file test" functions available in Perl are part of the programming language itself. Based on what you're saying and from the comments on this page, it may be that you have been "asked not to use external commands" because someone thinks that the -T flag is relying on something that belongs to the underlying environment and not the Perl language.
-T is part of the -X file test unary operators which are inherent to Perl:
http://perldoc.perl.org/functions/-X.html
Underlying the -T operator (specifically) is the function pp_fttext, which lives in pp_sys.c. These are part of the underlying code that comprises Perl, and you can verify this by looking in the root directory of the Perl source distribution:
http://www.perl.org/get.html
It may be the only way to do what you were originally asking (how to do this without -T) might be to do what you were asked not to do (use something external to Perl to perform the test).

cygwin sed substitution against commands in history

I couldn't find an answer for this exact problem, so I'll ask it.
I'm working in Cygwin and want to reference previous commands using !n notation, e.g., if command 5 was which ls, then !5 runs the same command.
The problem is when trying to do substitution, so running:
!5:s/which \([a-z]\)/\1/
should just run ls, or whatever the argument was for which for command number 5.
I've tried several ways of doing this kind of substitution and get the same error:
bash: :s/which \([a-z]*\)/\1/: substitution failed
As far as I can tell the s/old/new/ history substitution syntax only does simple string substitution; it does not support full regexes. Here's what man bash has to say:
s/old/new/
Substitute new for the first occurrence of old in the event line. Any delimiter can be used in place of /. The final delimiter is optional if it is the last character of the event line. The delimiter may be quoted in old and new with a single backslash. If & appears in new, it is replaced by old. A single backslash will quote the &. If old is null, it is set to the last old substituted, or, if no previous history substitutions took place, the last string in a !?string[?] search.
Never fear, though. There are in fact easier ways to accomplish what you are trying to do:
!$ evaluates to the last argument of the previous command:
# ls /etc/passwd
/etc/passwd
# vim !$
vim /etc/passwd
!5:$ evaluates to the last argument of command #5:
# history
...
5: which ls
...
# !5:$
ls
You can also use Alt+. to perform an immediate substitution equivalent to !$. Alt+. is one of the best bash tricks I know.
This worked for me using Bash in Cygwin (note that my which ls command was number 501 in my history list; not 5 like yours):
$(!501 | sed 's/which \([a-z]\)/\1/')
You could also do it this way (which is shorter/cleaner):
$(!501 | sed 's/which //')