Fish adventures in noglob, calculators and expressions
TL;DR: fish easy version below works, but needs quotes when expression is complex: cc 2+2
but cc 'floor(2.3)'
.
I’m continuing to move my useful snippets from zsh to fish (240620-2109 Fish shell bits), and the most challenging one was the CLI python calculator I really love and depend on, since it contained arguments with parentheses (which are fish expressions as well).
Basically: cc WHATEVER
runs WHATEVER
inside python, can do both easy math a la 2+2
and more casual statistics-y mean([2,33,28])
.
Before in zsh this was the magic function:
cc() python3 -c "from math import *; from statistics import *; print($*);"
alias cc='noglob cc'
Fish, easy version:
function cc
command python3 -c "from math import *; from statistics import *; print($argv);"
end
Works for easy cc 2+2
bits, but as soon as functions and therefore parentheses get involved (cc floor(2.3)
) it starts to error out.
[I] sh@nebra~/t $ cc mean([2,4])
fish: Unknown command: '[2,4]'
in command substitution
fish: Unknown command
cc mean([2,4])
^~~~~~^
[I] sh@nebra~/t $ cc mean\([2,4]\)
>>> mean([2,4])
3
[I] sh@nebra~/t $
(But I REALLY don’t want to do cc mean\([2, 3]\)
)
In the zsh snippet, noglob
meant basically “take this literally w/o expanding anything”, and it passed everything as-is to python, and this is what fails in my fish solution.
Noglob in fish is fun:
- The fish language — fish-shell 3.7.0 documentation on escaping characters
- Implement the noglob modifier · Issue #3504 · fish-shell/fish-shell:
If you wish to use arguments that may be expanded somehow literally, quote them. echo ‘’ and echo “” both will print the literal.
- The fish language on quotes:
- single quotes = no expansion of any kind
\'
for literals inside single
- double quotes = variable exp. (
$TERM
) & command substitution ($(command)
)\"
for literal"
s inside double
- within each other, no special meaning
- Let’s test:
echo (ls)
= ls output, one lineecho "$(ls)"
= ls output, multilineecho '(ls)'
=(ls)
echo "(ls)"
="(ls)"
- single quotes = no expansion of any kind
THEN
-
command python3 -c "from math import *; from statistics import *; print($argv);"
cc ceil\(2\)
+cc ceil(2)
-
-
`command python3 -c “from math import *; from statistics import *; print(’$argv’);”
- literally prints the passed thing w/o python eval, w/ same rules
-
OK can I do a variable then?
set pyc $argv
echo $pyc
command python3 -c "from math import *; from statistics import *; print($pyc);"
nope.
Bruteforcing the solution
(and learning to use fish loops mainly, of course there are better ways to do this.)
# list of simple, brackets, and parentheses + no, single, double quotes
# no space between nums in brackets, python interpreter would add them. [2,3] — literal, [2, 3] — parsed by python
set cmds \
'2+2' \
'\'2+2\'' \
'"2+2"' \
'[2,3]' \
'\'[2,3]\'' \
'"[2,3]"' \
'floor(2.3)' \
'\'floor(2.3)\'' \
'"floor(2.3)"'
function tcc
set pyc $argv
# command python3 -c "from math import *; from statistics import *; print" '(' "$pyc" ');'
# command python3 -c "from math import *; from statistics import *; print($pyc);"
command python3 -c "from math import *; from statistics import *; print($pyc);"
end
# loop through all test cases to see sth that works for all
for i in $cmds
echo $i:
echo " $(tcc $i)"
end
At the end, no additional literal quotes + initial command didn’t error out, and we came full circle:
set cmds \
'2+2' \
'[2,3]' \
'floor(2.3)'
# winner command!
function tcc
command python3 -c "from math import *; from statistics import *; print($argv);"
end
[I] sh@nebra~/t $ ./test_cc.sh
2+2:
4
[2,3]:
[2, 3]
floor(2.3):
2
- Double quotes in the python command mean only
$pyc
gets expanded $pyc
in the working versions have no hard-coded quotes- in CLI
tcc floor(2.3)
still fails — because like that it’s a command, not a string. In the file it was inside single quotes, as a string. So I can do this in the CLI as well.
So simple and logical at the end.
Final solution
function cc
echo ">>> $argv"
command python3 -c "from math import *; from statistics import *; print($argv);"
end
When using, quotes are needed only for complex bits (parentheses, *
etc.).
[I] sh@nebra~/t $ cc 2+2
>>> 2+2
4
[I] sh@nebra~/t $ cc [2,3,4]
>>> [2,3,4]
[2, 3, 4]
# no quotes
[I] sh@nebra~/t $ cc mean([2,3,4])
fish: Unknown command: '[2,3,4]'
in command substitution
fish: Unknown command
cc mean([2,3,4])
^~~~~~~~^
# with quotes
[I] sh@nebra~/t $ cc 'mean([2,3,4])'
>>> mean([2,3,4])
3
So I literally had to follow the advice from the first link I found and used single quotes in my initial command:
If you wish to use arguments that may be expanded somehow literally, quote them. echo ‘’ and echo “” both will print the literal.
Still, I learned a lot about fish in the process and honestly am loving it.