Brace expansion with range in fish shell - shell

In bash, I can do the following
$ echo bunny{1..6}
bunny1 bunny2 bunny3 bunny4 bunny5 bunny6
Is there a way to achieve the same result in fish?

The short answer is echo bunny(seq 6)
Longer answer: In keeping with fish's philosophy of replacing magical syntax with concrete commands, we should hunt for a Unix command that substitutes for the syntactic construct {1..6}. seq fits the bill; it outputs numbers in some range, and in this case, integers from 1 to 6. fish (to its shame) omits a help page for seq, but it is a standard Unix/Linux command.
Once we have found such a command, we can leverage command substitutions. The command (foo)bar performs command substitution, expanding foo into an array, and may result in multiple arguments. Each argument has 'bar' appended.

Related

Read file and run command in zsh

So I generally create job files with a list of commands in it. Then I execute it like so
cat jobFile | while read a; do $a; done
Which always works in bash. However, I've just started working in Mac which apparently uses zsh. And this command fails with "no such file" etc. I've tested the job file by running few lines from it manually, so it should be fine.
I've found questions on zsh read inbut they tend to be reading in from variables e.g. $a=('a' 'b' 'c') or echo $a
Thank you for your answers!
In bash, unquoted parameter expansions always undergo word-splitting, so if a="foo bar", then $a expands to two words, foo and bar. As a command, this means running the command foo with an argument bar.
In zsh, parameter expansions to not undergo word-splitting by default, which means the same expansion $a would produce a single word foo bar, treated as the name of the command to execute.
In either case, relying on parameter expansion to "parse" a shell command is fragile; in addition to word-splitting, the expansion is subject to pathname expansion (globbing), and you are limited to simple commands and their arguments. No pipes, lists (&&, ||), or redirections allowed, as everything will be treated as the command name and a sequence of arguments.
What you want in both shells is to simply treat your job file as a shell script, which can be executed in the current shell using the . command:
. jobFile
Why are you executing it in such a cumbersome way? Assuming jobFile is a file holding a sequence of bash commands, you can simply run it as
bash jobFile
If it contains a sequence of zsh commands, you can likewise run it as
zsh jobFile
If you follow this approach, I would however reflect in the name of the job file, what shell it is intended for, i.e.
bash jobFile.bash
zsh jobFile.zsh
and, if you write a job file so that it is supposed to be compatible with either shell, I would name it jobFile.sh.

Where is "key=value bash-script" usage documented?

I often see key=value bash-script to pass variables to a bash script. Here is an example:
$ echo $0
-bash
$ cat foo.sh
#!/usr/bin/env bash
echo "key1: $key1"
$ key1=value1 ./foo.sh
key1: value1
I have checked Bash Reference Manual. But I can't a description related to this usage.
Section 3.7.4 "Environment"
The environment for any simple command or function may be augmented temporarily by prefixing it with parameter assignments, as described in Shell Parameters. These assignment statements affect only the environment seen by that command.
Apparently the online version of the documentation fails to describe the syntax of a simple command completely. It reads:
3.2.1 Simple Commands
A simple command is the kind of command encountered most often. It’s just a sequence of words separated by blanks, terminated by one of the shell’s control operators (see Definitions). The first word generally specifies a command to be executed, with the rest of the words being that command’s arguments.
There is some information about the variable assignments that may precede a simple command on the section Simple Commands Expansion:
3.7.1 Simple Command Expansion
When a simple command is executed, the shell performs the following expansions, assignments, and redirections, from left to right.
The words that the parser has marked as variable assignments (those preceding the command name) and redirections are saved for later processing.
...
The text after the = in each variable assignment undergoes tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal before being assigned to the variable.
The manual page bundled with the shell seems to be better at this. It says (the emphasis is mine):
Simple Commands
A simple command is a sequence of optional variable assignments followed by blank-separated words and redirections, and terminated by a control operator.
You can always read the documentation of bash (and of any other CLI program installed on your computer) using man.
Type man bash on your terminal and use the usual keys (<spacebar>, b, /, q etc.) to navigate in the document. Behind the scenes, man uses your default pager program (which is, most probably, less) to put the information on screen.

Why are heredoc redirections not allowed before a while loop

Background
The POSIX Shell Command Language allows redirections to follow compound commands. The standard says (emphasis mine)
2.9.4 Compound Commands
The shell has several programming constructs that are "compound commands", which provide control flow for commands. Each of these compound commands has a reserved word or control operator at the beginning, and a corresponding terminator reserved word or operator at the end. In addition, each can be followed by redirections on the same line as the terminator. Each redirection shall apply to all the commands within the compound command that do not explicitly override that redirection.
For aesthetic reasons I want to put a heredoc before my while loop as in
<<'RECORDS' while
foo:bar
baz:quux
...
RECORDS
IFS=: read -r A B
do
# do something with A and B
done
because it makes the code easier to follow. However, it doesn't work in the shells I tried it (bash and dash). I get errors saying that the "while" command was not found and I assume that means that after the leading heredoc a simple command is expected and not a compound command.
I cannot move the heredoc to after read because then it reads the first line from a new heredoc on every iteration. I know that I can fix this by moving the heredoc to after done. I could also open a fd to a heredoc with exec before the loop and add a redirection to read.
My question
What's the reason redirections cannot occur before a compound command? Is there a shell that supports it since it's not explicitly prohibited by POSIX?
The Z shell (zsh) will accept this non-standard syntax. POSIX standardizes existing practice, and in the case of the shell, the reference implementation was the Korn shell (ksh88, or just ksh). Since that implementation supported only redirections following the compound statement that was what was standardized.
There are several ways you can write your loop in a more portable (and much easier to read) form. For example:
while
IFS=: read -r A B
do
echo $A $B
done <<'RECORDS'
foo:bar
baz:quux
RECORDS
is the commonest way I would do something like this. Or you could wrap the loop up in a function and redirect the input to the function:
loop()
{
while
IFS=: read -r A B
do
echo $A $B
done
}
<<'RECORDS' loop
foo:bar
baz:quux
RECORDS
This allows you to mix up the call to the function in amid the data as you originally wanted (I still don't really understand why you think this is clearer:-) )
Both these techniques work with bash, dash, ksh, ksh93, yash and zsh.

Bash tilde not expanding in certain arguments, such as --home_dir=~

Bash is not expanding the ~ character in the argument --home_dir=~. For example:
$ echo --home_dir=~
--home_dir=~
Bash does expand ~ when I leave out the hyphens:
$ echo home_dir=~
home_dir=/home/reedwm
Why does Bash have this behavior? This is irritating, as paths with ~ are not expanded when I specify that path as an argument to a command.
bash is somewhat mistakenly treating home_dir=~ as an assignment. As such, the ~ is eligible for expansion:
Each variable assignment is checked for unquoted tilde-prefixes immediately following a : or the first =. In these cases, tilde expansion is
also performed.
Since --home_dir is not a valid identifier, that string is not mistaken for an assignment.
Arguably, you have uncovered a bug in bash. (I say arguably, because if you use set -k, then home_dir=~ is an assignment, even though it is after, not before, the command name.)
However, when in doubt, quote a string that is meant to be treated literally whether or not it is subject to any sort of shell processing.
echo '--home_dir=~'
Update: This is intentional, according to the maintainer, to allow assignment-like argument for commands like make to take advantage of tilde-expansion. (And commands like export, which for some reason I was thinking were special because they are builtins, but tilde expansion would have to occur before the actual command is necessarily known.)
Like chepner says in their answer, according to the documentation, it shouldn't expand it even in echo home_dir=~. But for some reason it does expand it in any word that even looks like an assignment, and has done so at least as far back as in 3.2.
Most other shells also don't expand the tilde except in cases where it really is at the start of the word, so depending on it working might not be such a good idea.
Use "$HOME" instead if you want it to expand, and "~" if you want a literal tilde. E.g.
$ echo "~" --foo="$HOME"
~ --foo=/home/itvirta
(The more complex cases are harder to do manually, but most of the time it's the running user's own home directory one wants.)
Well, that's because in echo --home_dir=~, the '~' does not begin the word and the output of echo is not considered a variable assignment. Specifically, man bash "Tilde Expansion" provides expansion if
If a word begins with an unquoted tilde character (~); or
variable assignment is checked for unquoted tilde-prefixes immediately following a : or the first =.
You case doesn't qualify as either.

Is it necessary to quote command substitutions during variable assignment in bash?

Nearly everywhere I've read, including Google's bash scripting style guide mention to necessity of quoting command substitutions (except when specifically desired of course).
I understand the when/where/why of quoting command substitutions during general use. For example: echo "$(cat <<< "* useless string *")" rather than echo $(...)
However for variable assignments specifically, I have seen so many examples as such:
variable="$(command)"
Yet I have found no instances where variable=$(command) is not equivalent.
variable="$(echo "*")" and variable=$(echo "*") both set the value to '*'.
Can anyone give any situations where leaving the substitution unquoted during variable assigment would actually cause a problem?
The shell does not perform word splitting for variable assignments (it is standardized that way by POSIX and you can rely on it). Thus you do not need double quotes (but you can use them without making the result different) in
variable=$(command) # same as variable="$(command)"
However, word-splitting is performed before executing commands, so in
echo $(command)
echo "$(command)"
the result may be different. The latter keeps all multi-space sequences, while the former makes each word a different argument to echo. It is up to you to decide which is the desired behavior.
Interesting shell quirk: there is one more place where quoting a substitution or not makes no difference, namely the expression in a case expr in construct.
case $FOO in
(frob) ...;;
esac
is indistinguishable from
case "$FOO" in
(frob) ...;;
esac
When using BASH, those two lines are 100% equivalent:
variable="$(command)"
variable=$(command)
while these two aren't:
echo $(command)
echo "$(command)"
Alas, the human brain isn't a computer. It's especially not as reliable when it comes to repeat a job.
So chances are that if you mix styles (i.e. you quote when you use command arguments but you don't quote when you assign a variable), that once in a while, you'll get it wrong.
Worse, once in a while you will want the command result to be expanded into individual words. And the next guy who reads your code will wonder if he's looking at a bug.
Conculsion: Since the first two lines are the same, it's easier on the average brain to always quote $() (even when it's not necessary) to make sure you always quote when you have to.
Testing with so many different commands, it is the same. Can't find a difference.

Resources