I'm trying to write a function which will change the extension for all matching files in the current directory:
function chext
if count $args -eq 2 > /dev/null
for f in (ls *$argv[1])
mv (basename $f $argv[2]) (basename $f $argv[1])
end
else
echo "Error: use the following syntax"
echo "chext oldext newext"
end
end
I keep getting this output though:
(*master) λ chext markdown txt
No matches for wildcard '*$argv[1]'. (Tip: empty matches are allowed in 'set', 'count', 'for'.)
~/.config/fish/config.fish (line 1): ls *$argv[1]
^
in command substitution
called on line 60 of file ~/.config/fish/config.fish
in function 'chext'
called on standard input
with parameter list 'markdown txt'
I know there must be a way to interpolate the variable and keep *
as a wildcard, but I can't figure it out. I have tried using (string join '*' $argv[1])
, but it turned the wildcard into a string.
I did get this to almost work:
function chext
if count $args -eq 2 > /dev/null
for f in (ls (string join * $argv[1]))
mv (basename $f $argv[2]) (basename $f $argv[1])
end
else
echo "Error: use the following syntax"
echo "chext oldext newext"
end
end
It removed the extensions from all of my files but didn't add the new one, then gave me an error which was all of the file names combined and said file name too long.
UPDATE:
I'm sure there is a way to get the correct ls
working, but I did get this working successfully:
if count $args -eq 2 > /dev/null
for f in (ls)
mv $f (string replace -r \.$argv[1]\$ .$argv[2] (basename $f))
end
else
echo "Error: use the following syntax"
echo "chext oldext newext"
end
end
The answer is in the message:
No matches for wildcard '*$argv[1]'. (Tip: empty matches are allowed in 'set', 'count', 'for'.)
That means
for i in *argv[1]
works, as does
set -l expanded *argv[1]
If you try to launch any other command with a glob that doesn't match anything, fish will skip the execution and print an error.
Your function has a number of other issues, allow me to go through them one by one.
if count $args -eq 2 > /dev/null
count
does not take the "-eq 2" argument. It will return 0 (i.e. true) whenever it is given an argument, so this will always be true.
What you tried to do was if test (count $args) -eq 2
.
What also works (and what I happen to like more) is if set -q argv[2]
(also, it's "argv", not "args").
for f in (ls *$argv[1])
Please do not use the output of ls
. It doesn't have as much problems in fish as it does in bash (where it'll break once a file has a space, tab or newline in the name), but it's unnecessary (it launches an additional process) and still has an issue - it'll break when a file has a newline in its name.
Just do for f in *$argv[1]
, which will also nicely solve your question.
mv (basename $f $argv[2]) (basename $f $argv[1])
I'm not sure what basename
you are using here, but mine just removes a suffix. So if you did chext .opus .ogg
, this would execute
mv (basename song.opus .ogg) (basename song.opus .opus)
which would resolve to
mv song.opus song
Personally, I'd use string replace
for this. Something like
mv $f (string replace -- $argv[1] $argv[2] $f)
(Note: This would do the wrong thing if the extension string appears before, e.g. if you had a file called "f.ogg-banana.ogg" only the first ".ogg" would be changed. You might want to use regular expressions for this: string replace -r -- "$argv[1]\$" $argv[2] $f
)