2

I have the following line in my .bashrc:

#!/usr/bin/env bash
alias see='nano --view $1'

But now shellcheck is producing a high priority warning:

Aliases can't use positional parameters, use a function instead.

Reasonable except for one thing: it works fine! What's going on?

αғsнιη
  • 41,407
Vorac
  • 3,077

2 Answers2

4

No, and it works because $1 is emptyusually [1] and not quoted. The resulting command from see foo would be see foo.

If you quoted it however as in:

alias see='nano --view "$1"'

It would become see <empty> foo and you would open a new buffer in addition to foo in nano.


[1] $1 is expanded to whatever the value of the shell's first positional parameter is - in an interactive shell that's usually nothing (since interactive shells aren't typically invoked with positional parameters), however there's nothing to stop it from being set to something arbitrary (for example with the set shell builtin / help set) - set -- foo bar; see baz for example would open foo then baz. (Freely copied from comment by @steeldriver as it is an important note.)

So do not trust it is always empty.


You can also try:

$ alias e='echo @$1@'
$ e foo  <-- would become: echo @$1@ foo
@@ foo
|  |
|  +--- foo is next string
+------ $1 is empty

Under the hood so to speak you would see something like:

execve("/bin/nano", ["nano", "--view", "foo"], 0xabc...)

and for the quoted version:

execve("/bin/nano", ["nano", "--view", "", "foo"], 0xabc...)

In the main function of nano

int main(int argc, char *argv[])

argc, (argument count), would be 3 and 4 respectively and argv, (argument vector / array), would be the strings in the square brackets.


You can use:

alias see='nano --view'

Then by issuing:

$ see foo

foo will be $1, (not really $1 but in comparison), and the command will expand to:

nano --view foo

If you need to restrict nano to only open $1, (first argument), you would need a function because:

see foo bar

would expand to nano --view foo bar

Then you could use something like:

see() {
    nano --view "$1"
}

Then you would discard any argument above $1 and when execve is called it would always be:

execve("/bin/nano", ["nano", "--view", "filename"], 0xabc...)

where filename is $1 in bash. Nano would not see $1, only the value. Bash takes care of that.


When one use a shell function the arguments are defined as positional parameters, $1, $2, $3, …, and the function has access to these. An alias is not treated as a function hence it does not have access to these parameters / they do not exists.

Try:

alias p1='echo "$# $1 END"'
p2() { echo "$# $1 END"; }

Result:

$ p1 a b c
0  END a b c

$ p2 a b c 3 a END



In general, Bash manual states:

For almost every purpose, shell functions are preferred over aliases.

and

There is no mechanism for using arguments in the replacement text, as in csh. If arguments are needed, a shell function should be used (see Shell Functions).


For the shellcheck write up, see the wiki for SC2142.

ibuprofen
  • 2,890
  • So what is happening is $1 gets expanded to ... nothing? (reminder: we are talking about (a file sourced by) .bashrc) – Vorac Jun 20 '21 at 02:41
  • 1
    @Vorac $1 is expanded to whatever the value of the shell's first positional parameter is - in an interactive shell that's usually nothing (since interactive shells aren't typically invoked with positional parameters), however there's nothing to stop it from being set to something arbitrary (for example with the set shell builtin) - set -- foo bar; see baz for example would open foo then baz. – steeldriver Jun 20 '21 at 11:14
  • @ibuprofen sure, no problem – steeldriver Jun 20 '21 at 20:17
0

Aliases do not take arguments, they are just copied as is. They seem to (sometimes) work (sort of). But it doesn't.

Use a shell function.

vonbrand
  • 18,253
  • Thanks. Can I please also get a hypothesis why this one appears to work == "has worked for years on all kinds of files and respects the --view flag"? – Vorac Jun 20 '21 at 02:23