Backticked strings getting unexpectedly executed as external commands
Describe the bug
According to the docs "Working with strings", backticks are one of the ways to create a string. Let's try:
~: let a = "hello" + "world" ; $a
helloworld
~: let a = "hello" + `world` ; $a
helloworld
~: let a = `hello` + "world" ; $a
Error: nu::shell::external_command
× External command failed
╭─[entry #32:1:1]
1 │ let a = `hello` + "world" ; $a
· ───┬───
· ╰── executable was not found
╰────
help: No such file or directory (os error 2)
This can lead to counter-intuitive things like:
$ cat ~/bin/wipeeverything
#!/usr/bin/env bash
#rm -rf /
echo "Wiping everything"
$ cat supertool.nu
let messages = [`hello` `world`] # creating strings
let message = `wipeeverything` # creating string (big mistake)
let combined = $messages | append $message
$combined
$ nu supertool.nu
╭───┬───────────────────╮
│ 0 │ hello │
│ 1 │ world │
│ 2 │ Wiping everything │
╰───┴───────────────────╯
How to reproduce
See description.
Expected behavior
If it's one of the ways to create strings, then it should behave like all other strings. If let a = "hello" + "world" doesn't run the hello executable, then neither should let a = `hello` + "world".
Screenshots
No response
Configuration
| key | value |
|---|---|
| version | 0.89.0 |
| branch | |
| commit_hash | |
| build_os | linux-x86_64 |
| build_target | x86_64-unknown-linux-gnu |
| rust_version | rustc 1.75.0 (82e1608df 2023-12-21) |
| rust_channel | stable-x86_64-unknown-linux-gnu |
| cargo_version | cargo 1.75.0 (1d8b05cdd 2023-11-20) |
| build_time | 2024-02-01 13:42:12 +01:00 |
| build_rust_channel | release |
| allocator | mimalloc |
| features | default, sqlite, trash, which, zip |
| installed_plugins |
Additional context
No response
I can't remember if it was mentioned in Discord or in another issue, but I believe backticks were changed to denote file path strings only. The doc just wasn't changed to reflect the implementation.
You can see this by tab-completing a filename with a space, which will be backtick-quoted.
I think the execution of the file is a side-effect of evaluating a path expression, even when it's on the path and not in the currently directory.
I'm not a huge fan of this, but maybe its not a big deal. Backticks to execute/evaluate something were one of the worst features of older shells, leading to nesting and readability issues. On the bright side, I don't think this result will in the "old style" evaluation, just execution of the command.
For instance, ls executes the binary, but ls -l results in a "not found" error.
Yes I think @NotTheDr01ds you nailed it. /some/path is in fact cding to it.
Another interesting aspect of those executing backticks is that although as you mention flags don't seem to be accepted, binaries with a space would (edge case I know but still valid). And I'm not sure to get in which context those are executed:
❯ which "git ls"
╭───┬─────────┬──────┬────────╮
│ # │ command │ path │ type │
├───┼─────────┼──────┼────────┤
│ 0 │ git ls │ │ custom │
╰───┴─────────┴──────┴────────╯
❯ `git ls`
Error: nu::shell::external_command
× External command failed
╭─[entry #4:1:1]
1 │ `git ls`
· ────┬───
· ╰── command 'git ls' was not found but it exists in module 'git-base'; try importing it with `use`
╰────
help: program not found
I didn't read closely enough.
The wipeeverything potential is certainly problematic.
Context (AFAICT) in case it isn't common knowledge)
I dug through the parser and old PRs to understand the behavior. "Bare words" in this context refer to how identifiers without quotes are treated. Backticks are treated identically to strings everywhere in the parser. The only differences I've found (so far) are in run_external (no globbing) and (relevantly) the REPL:
https://github.com/nushell/nushell/blob/0d518bf8136fd9793dd13c5369ff65288242c682/crates/nu-cli/src/repl.rs#L589-L591
It strips the delimiters to behave like an identifier. I remember this being 1 of my first stumbling blocks when I tried to do Light theme enabled | print in my config. I didn't consider the first word must be a command. That's only a doc issue.
But in the case of backticks I agree they should either always be parsed as bare words or never. A straightforward solution might be trimming the delimiters during parsing. I also find it confusing that `ls` is ^ls vs internal ls.
The thing I haven't figured out is their real world use case.