Xonsh: I don't remember how to write a for loop in Bash [video]
youtube.com
youtube.com
....just so the author doesn't have to look at StackOverflow, here is his Python example in the video:
for i in range(5):
if i:
print(i)
Here it is in POSIX Shell/Bourne Shell/Bash/whatever: for i in 1 2 3 4 5
do
echo $i
done
The fancy version: for i in `seq 1 5` ; do
if [ $i -gt 0 ] ; then
echo $i
fi
done
Fancier: for i in $(seq 1 5) ; do
[ $i -gt 0 ] && echo $i
done
Mucho Fancy (Bash-only): while read -r NUM ; do
[ "$NUM" -gt 0 ] && echo "$NUM"
done < <(seq 1 5)
Uber Fancy: Internal Seq w/Arrays & References (Bash v4+ only): _internal_seq () {
declare -n aref="$1"
local counter="$2" end="$3"
while [ "$counter" -le "$end" ] ; do
aref+=("$counter")
counter="$((counter+1))"
done
}
declare -a numbers=()
_internal_seq numbers 1 5
for i in "${numbers[@]}" ; do
echo "$i"
done
The version I actually use as a one-liner: for i in `seq 1 5` ; do echo $i ; done A sequence expression takes the form {x..y[..incr]}, where x and y are either integers or
single characters, and incr, an optional increment, is an integer. When integers are sup-
plied, the expression expands to each number between x and y, inclusive. Supplied integers
may be prefixed with 0 to force each term to have the same width. When either x or y
begins with a zero, the shell attempts to force all generated terms to contain the same
number of digits, zero-padding where necessary. When characters are supplied, the expres-
sion expands to each character lexicographically between x and y, inclusive, using the
default C locale. Note that both x and y must be of the same type. When the increment is
supplied, it is used as the difference between each term. The default increment is 1 or -1
as appropriate.
...although I just tested it, and the increment and 0-padding only work in non-interactive mode ='(Works for me...
$ echo $0 # determine current shell (alternative: "ps -p $$")
bash
$ echo {01..100..20}
001 021 041 061 081
But note that `{x..y}` has a major limitation: it doesn't support variables. Example: $ n=5
$ echo {1..$n}
{1..5}
# intended output: 1 2 3 4 5
The `for ((...))` syntax mentioned by @zazaulola supports variables (it works in bash and zsh).For completeness, here is a POSIX sh approach that doesn't require `seq` (in fact I notice this is what your "Uber Fancy" version uses internally):
i=1
while [ "$i" -le 5 ]; do
echo "$i"
i="$((i+1))"
done
(but note that `continue` won't work properly). var=1234
echo `echo {1..$var}`With an array:
var=12
declare -a array=()
eval "array=( {01..$var} )"
echo "${array[@]}"
# 01 02 03 04 05 06 07 08 09 10 11 12 for((x=1;x<=5;x++))
do if [[ -v x ]]
then echo $x
fi
doneAside: [[ -v x ]] tests if the variable x is set. It should probably be [[ x -ne 0 ]] to match the example from the parent.
In sh ? In bash ? In zsh ? I am very glad if it is so in any of them
for i in 1 2 3 4 5; { echo $i; }
for ((i=1;i<=5;i++)) { echo $i; }
(It doesn't work with `while` loops, at least not in bash.)while (ls) { echo Hello; echo World }
does work in zsh
But if your break a line after {, then you get while> prompt and } does not trigger execution
Personally, I never use naked loops if I can avoid them. Either foreach or higher order functions if the language has them.
seq 5 | xargs -n1 <...> seq 5 seq 5 | xargs -n1 <...>Compare:
for i in {1..5}; do echo file_$i; done
with
seq 5 | xargs -I % echo file_%
I had to look up the manpage for xarg's -I, while the for loop uses a small number of orthogonal features entirely in my "working set".
This is also why people "uselessly" use cat.
For myself I can generally write basic python (or c) without having to double check. Shell not so much. Eg Does the semi colon come before or after the do? Then there's the quoting rules and expansions etc.
If you read the man page from top to bottom, pretty much all your questions will go away. Bash is only as confusing and mysterious as every other language. Imagine trying to use C++ without ever reading a book on how C++ works. Bash is much simpler! But you should read the man page!
? Expands to the exit status of the most recently executed foreground pipeline.
$ foo="$(echo foobar ; exit 3)" ; ret=$? ; echo "output: $foo" ; echo "return status: $ret"
output: foobar
return status: 3
Also, PIPESTATUS is an array with the return status from each command in a pipeline.It runs your bash scripts but you can also opt into correct error handling. The simple invariant is that it doesn't lose an exit code, and non-zero is fatal by default.
Oil 0.10.0 - Can Unix Shell Error Handling Be Fixed Once and For All?
https://www.oilshell.org/blog/2022/05/release-0.10.0.html
This took quite awhile, and I became aware of more error handling gotchas than I knew about when starting the project:
https://www.oilshell.org/release/latest/doc/error-handling.h...
e.g. it's impossible in bash to even see the error status of a process sub like diff <(sort left.txt) <(sort OOPS)
If you have bash scripts that you don't want to rewrite, try
1) run them with OSH
2) Add shopt --set oil:upgrade at the top to get the error handling fixes.
Tell me what happens :) https://github.com/oilshell/oil
I spent a long time on that, but the post didn't get read much. I think it's because it takes a lot of bash experience to even understand what the problem is.
How does this work? It seems magic
oil$ shopt -p oil:upgrade
shopt -s command_sub_errexit
shopt -u dashglob
shopt -s errexit
shopt -u expand_aliases
shopt -s inherit_errexit
...
Some affect parsing and some affect execution.OSH is a bash-compatible shell, from scratch! :) Let me know what happens
$ cat foo.sh
#!/usr/bin/env bash
set -euxo pipefail
declare x="$(err)"
$ shellcheck foo.sh
In foo.sh line 3:
declare x="$(err)"
^-- SC2034 (warning): x appears unused. Verify use (or export if used externally).
^-- SC2155 (warning): Declare and assign separately to avoid masking return values.
For more information:
https://www.shellcheck.net/wiki/SC2034 -- x appears unused. Verify use (or ...
https://www.shellcheck.net/wiki/SC2155 -- Declare and assign separately to ...
And the Bash man page mentions it a couple times, particularly around Pipelines, pipefail, Lists, and Compound Commands. You can also check exit status in the PIPESTATUS array.I almost never use set -o pipefail, and often not even set -e. Your scripts end up failing for mysterious reasons and it takes forever to figure out which part of which pipeline failed. Instead, I simply check the result of the end of the pipe (did it return any data? does it look valid?) and that is good enough 99% of the time.
this means you need to write everything separate, test doesn't parse, plus it needs the closing bracket if you use the alias
but if-then works with any expression, and you put ; after expressions
if test 1 -eq 2 ; then echo "oh noes" ; fiI'll make sure to either use test or [[ and never [ again so that I remember the difference. And I am currently reading man test
(I continue to use [[ and never look back, just forward, trying to picture the shining city on a conveniently sloped hill, where people live without fear in their hearts, where they have compile time checking for their scripts, and the compiler gently pushes them toward sane error handling. Not too much, but definitely not too little. Something like Rust's Result type.)
Some code style guides for Linux distros or work places like Google actually enforce this!
2
> Do I use == or -eq?
For string comparison, use '='. For arithmetic comparison, use -eq, -gt, -lt, etc.
> Where does the semicolon go again?
Whenever you're putting multiple lines on one. If you use multiple lines, you don't need semicolons.
For example:
if [[ "$result" = 'SUCCESS' ]] && [[ "$count" -gt 0 ]]
then
echo 'Hooray!'
fiIf you want a much-reduced version, read man dash instead. It is very small and close to POSIX semantics. You can then later go find the Bashisms.
It’s fine to prefer one tool over the other but bashing the one you’re less familiar with is unnecessary :)
Except with Bash it really isn't worth it. Learning PowerShell is much better for long scripts.
Just wait a couple decades and languages slowly improve or they die. Sometimes it takes a long time (in comparison to a person's lifespan) and that's ok.
This form `Verb-LongNoun` for function names used in PS is somewhere between alien and maddening. It's difficult to type, difficult to remember, and isn't an obvious improvement over `lowercasename` which is typical of basically every other shell.
I guess the idea is that you don't have to worry about naming collisions with programs in your path? That's never been an issue for me when writing shell scripts though, not even once.
I’m not OP but things I like about PowerShell:
- Objects instead of text
- The entire C# std lib is accessible
- One tool with the same UX vs several different tools with different UX.
But the single best feature of PS is that you pipe structured objects to the next command, instead of raw text that must be parsed, which is prone error. Every command output object can be exported to JSON with ConverTo-JSON. You can export the entire Active Directory as JSON with
Get-ADObject -Filter * | ConvertTo-JSON > AD.json
Some more examples.
Get-ChildItem -Path *.txt | Where-Object {$_.length -gt 10000} | Sort-Object -Property length | Format-Table -Property name, length
Get-Item -Path HKLM:\Software\MyCompany | New-ItemProperty -Name NoOfEmployees -Value 8124
Get-Process | Sort-Object -Property handles
I agree with that, but other advanced shells (xonsh, elvish, or even fish) also have this feature.
Bash has been around for 33 years. Much of its syntax has been around for 52 years through sh.
Ofc this is bigger thinking than the Powershell vs Bash debate, per se. Powershell is more useable as a language, if not by pure syntactic maturity, than by features alone. There will always be someone who will disagree with the obvious, but there's no doubt that the algol syntax is going away...however long that will take.
I’m not buying what you’re selling tbh.
No it is an opinion, and IMHO the RoI for time invested in learning bash is far less than Python or Powershell.
$ bash
/bin/ksh: bash: not found
strike one... $ pkglocate powershell | grep \^power
$
strike two...To me the issue is why learn a different language for scripting? Python is the obvious replacement, but even Java write scripts in, I'm pretty sure any major language can compile and run easily.
Of course python comes with its own package management and system version clashes
Shells are pretty decent at piping and spawning subprocesses. They suck at anything algorithmic, numeric, etc. Thankfully those tasks can usually be handed off to a separate tool (e.g. classic Unix text processing tools, like sed/grep/cut/etc., or more recent data processing tools like jq, mlr, etc.)
Face it folks, it’s not bash’s fault if you use it seldom enough that you need a reminder of the syntax when you do. Nothing wrong with that!
I think your comment convinced me to never use bash again. Now onto finding a replacement... If people say they use bash because it is preinstalled they can go screw themselves. Install whatever replacement I'm going to pick and shut up.
Except I have no issues with other programming languages. Bash is really different. I only tolerate it because it comes pre-installed on just about every system.
I also have issues with COBOL.
Sometimes a language is just too inelegant to pour time into learning it.
It’s about making a new language (and shell) that tries to bring what’s good about a nice shell to Python so that when we hit our “this bash script is too big/complex and I need a better language” threshold, we can pick up Python (a reasonable choice) but retain some of the ergonomics of bash for calling and piping data to/from external programs, which Python simply doesn’t have (where subprocess + shlex + stdlib utils just don’t cut it IMO).
From my minimal experience/playing around, xonsh seems quite nice and I find it a very believable future where it is a mainstream system scripting tool.
Another thing to keep in mind is that AFAICT it is coming out of the researchy-sciency crowd where Python is mainstream and bash a necessary evil. Finding a way to make subprocess calls easy seems an obvious win in that context.
Going even further, how nice would it be if something similar were part of the Python standard library? Being a wonderfully ergonomic shell script alternative seems like an instance where python could finally be the actual best at something.
However, I'm about to do the same lol, and come out as a bash apologist against the sea of derision here. I've spent a decade writing code in maybe 10 different languages, and bash is still what I write the most of -- and its by choice! If you're writing a lot of code that calls other code, bash is kind of the default choice, since that's literally what the shell is for.
Once you spend enough time with it, I actually find bash to be surprisingly elegant in what it affords the author in its terseness. Combined with other CLI utilities' own concision, it's an incredibly powerful environment. Sure it can start to appear arcane to unfamiliar folks, but you can work to make it significantly more readable than most people would have you believe. And with e.g. shellcheck, you can do away with a great deal of common footguns.
I asked him about it, and he said, "It is a lot of code, but it does a lot. And if I'd written it in C++ it would have been a lot more code than that!"
We were a Python shop.
display the blame for all lines of all files per folder | sort by name | sum counts by name
A single command-line can contain all the bash code, since the necessary tools are all available and can be placed together in a pipeline.
Plus, being a Python shop, any of us could have maintained a Python version, instead of the one engineer who was truly a Bash wizard.
Some common sense and manual work might have been even better: just ask everyone in the company to identify the code areas they were familiar with and manually populate the CODEOWNERS files, instead of spending months on a mostly-failed attempt to automate the whole thing.
Or, just teach people how to use git blame!
After all, many of us touched many different parts of the code. Git blame would tell you immediately who was familiar with the different bits, at a much more granular level than a CODEOWNERS file in a directory.
But, I have found that one of those is super-duper-extremely important: know when not to program! You can just ask other SMEs (subject matter experts) for the answer!
At least while he was writing his 7500 lines of bad code, he wasn't doing any other damage. You can throw it out and get somebody else to write a good one in a week.
take the output of
`git ls`
group it by directory
iterate over the grouped directories passing each file to
`git log --follow $1 | git shortlog -sn`
sum the numbers next to each name
sort the name + number combos within the directory.
print the info for each directory.
I really prefer to write no bash at all its so confusing.
Recently I started using TS for scripting. Not the best, but Ammonite (a Scala scripting env) is a bit too big of a lift.
[1] -- https://google.github.io/styleguide/shellguide.html#when-to-...
ls | awk '{print "command " $1}' | bash
Somehow the syntax to write loops in bash evades my memory (half because it is unconventional, half because I don't write them as often as I do in other languages) I know there is xargs which I am supposed to use instead of that above pipeline but I find myself having to read the whole man page every time I use it. As wrong as it, awk-bash pipelines are concise and easy to write.So anyway my co-worker did a for loop directly as a CLI command, probably using Tcsh at the time but it stuck with me.
It was like a “wow you can just”:
for F in `seq 1 10`; do
echo $F
done
And it stuck with me because he used variable F and to this day that’s my go to variable when just blurting out a bash/zsh for loop at the CLI. for I in {1..10}; do
....
doneFor some reason, I cannot remember this structure.
P.s. never had this problem with PHP, Python, Java...
P.s.2 I have this shortcut for batch (Windows) and PowerShell, too. :)
Allegedly the guy who created bash had a bunch of C macros that made C more algol68-like.
#define IF if(
#define THEN ){
#define ELSE }else{
#define ELSEIF }else if(
#define END }
#define WHILE while(
#define FOR for(
These are described as "non-syntactic macros" and are frowned on these days, but it was the Wild West in the early '70s.The biggest problem really seems to be that they wanted to avoid the use of esoteric syntax like double quotes and brackets, which resulted in a very unspecific syntax that is continually uncertain of if you are type strings or commands, so we have to use even more quotes and brackets and things of that nature.
It really makes me wonder why no one seemed to be working on guiding Linux over to some JIT compiled C scripting like TempleOS uses.
They still are, in a sense. [ is a synonym for test, and ] is a mandatory no-op argument to it, when used as a “built-in” syntax. You can use [ outside of if as well, like
[ <condition> ] && <then-action>
test <condition> && <then-action>
https://linux.die.net/man/1/testalias jc='python3.10 -m jupyter console'
It still shows python 3.9 if I do print(sys.version)
which python3.10
Saw this vid: https://youtu.be/xqQCOigizw4
At some point, I think we have to protect the ability for a human to understand and reason about a technology or a piece of software. Thus the need for better and more economic languages etc.
To sweep the the difficulties of reasoning about a language under the rug of a non-human AI tool that itself isn't providing human readable information on the software or language in question seems to be, quite frankly, an egregious shortcut for short-term gains.
Whatever gains and utility there are in Copilot, bugs creeping in without the developer even understanding them is exactly what many would fear would happen with it. More broadly though, training the developer to be agnostic or ignorant of the issues with the very technology they're using because Copilot is "taking care of it" surely has some negative knock-on effects in terms of developers being able to manage and improve their tech-stacks.
Programmer jobs are safe.
Brogrammer jobs are not.
It's just a very intelligent autocomplete- your creativity and intelligence is still required.
I still dislike Perl (it has pointers! pointers!), but its syntactic sugar for running shell commands and regexes makes shell-style scripting easier than Python.
Or maybe you’re mistaking it for pointer arithmetic?
func %h;
sub func {
my %h = @_;
Syntax for putting a hash inside an array, or vice-versa?None that I know of. But you can put a hashref into a list and vice versa:
push @a, \%h; # or {…}, $hr
$h{key} = \@a; # or […], $ar
How do you get the keys of a hash that's inside an array? keys %{$a[0]}
Your question could also mean keys %{{@a}}
though less likely.But my perl fu weakened after years of wandering and I also remember reading that refs became first-class for common operators and functions (including keys), so dereferencing is not mandatory anymore. Not sure if that’s true, could be a dream easily.
func %h;
sub func {
my %h = @_;
Note that the above only works if %h is the only parameter to the function, because your solution swallows the entire parameter array. Otherwise you must pass a hashref.In any case, these are examples of my discontent with Perl's syntax, which gets unreasonably complicated for non-trivial datastructures. To be clear, Perl's references, the \@a or \%h with the backslashes, are what I derisively call "pointers", because they introduce the same kind of complexity.
It's a problem when you must use different syntaxes to update/refer to a hash, depending on the scope you're using it:
$hash{$key}
vs $hashref->{$key}
The first syntax is if you have direct access to the %hash, such as the scope that created it. The second syntax is whenever you're forced to pass the %hash as a ref, for example because it's one of many parameters to a function. (And the hashref syntax I used is sugar! The basic syntax, demonstrated with the "hash-in-array" example, is worse)It's a problem when you have to "cast" a type, like in `keys %{$a[0]}` (for the audience: where $a[0] refers to a hashref inside an array, which has to be dereferenced via the %{} syntax, to be used as a hash). This is a simple example, but replace $a[0] with a more sophisticated datastructure, and things get exponentially unwieldy.
The above make it harder to keep track of data as you're browsing code.
In contrast, Python's everything-is-call-by-reference approach means that, whatever the scope, you can refer to something the same way. There never is differing syntax between 'something' and 'a reference to something'.
This is intriguing, but I can't find any indication of this in Perl's own canonical `perlref` doc, or elsewhere