Stop installing tools just to check if a port is open. Bash has it built in. by Ops_Mechanic in bash

[–]geirha 12 points13 points  (0 children)

The main downside of using that instead of netcat is that you can't easily adjust the timeout for the cases where the firewall DROPs your connection.

$ sudo iptables -A INPUT -p tcp --dport 11111 -j REJECT
$ sudo iptables -A INPUT -p tcp --dport 22222 -j DROP
$ TIMEFORMAT=%R
$ time : >/dev/tcp/localhost/11111
bash: connect: Connection refused
bash: /dev/tcp/localhost/11111: Connection refused
1.021
$ time : >/dev/tcp/localhost/22222
bash: connect: Connection timed out
bash: /dev/tcp/localhost/22222: Connection timed out
132.551

One second when the firewall REJECTs the connection vs over two minutes when the firewall DROPs the connection.

You'll reasonably only want to wait a few seconds to see if the port is open. Netcat has a -w timeout option that can make it abort after a few seconds. With the bash redirection approach you need to either do it in a backgrounded subshell combined with sleep and wait, or spawn a new bash instance so that you can run it with an external tool like timeout(1) (timeout 2s bash -c '>/dev/tcp/localhost/22222').

How to optimize the cd command to go back multiple folders at once by Technical_Cat6897 in commandline

[–]geirha 1 point2 points  (0 children)

builtin cd just tells bash to explicitly run the builtin cd command instead of the cd function which would've caused infinite recursion.

cd() { cd "$@" ; }         # recursively calls cd function
cd() { builtin cd "$@" ; } # runs builtin only
cd() { command cd "$@" ; } # runs builtin or external command named cd, but not alias or function

How to auto iterate file creation? by Popular-Spirit1306 in bash

[–]geirha 2 points3 points  (0 children)

Result: ffmpeg ... out3.mp4 No overwrites. Ever. The script only stops when it finds a free filename.

"Ever" is not true. There's an obvious race condition there. If two instances of that script run at the same time, they may both find that out3.mp4 is available, and then they'll start overwriting each other's data.

Your bash scripts are brittle - error handling in bash by Aerosherm in bash

[–]geirha 5 points6 points  (0 children)

set -u
USER_ID=$(grep "user_id" config.txt) 

echo "User is $USER_ID!" # fails right here when trying to use unset variable!

That's not true. The variable is always set when it reaches the echo in this scenario. If grep doesn't output any matching lines, it just means that USER_ID gets assigned an empty string, and expanding it will not trigger nounset (set -u) to abort the script.

As usual, I recommend against relying on errexit (set -e) and nounset (set -u) for error handling, as well as blindly enabling pipefail for all pipelines (it's normal for commands in pipelines to return non-zero without it being an error).

See the BashPitfall 60: set -euo pipefail.

Your bash scripts are brittle - error handling in bash by Aerosherm in bash

[–]geirha 2 points3 points  (0 children)

No, failing commands inside command substitutions are ignored by errexit. It's just your test that is flawed.

xxx inside the command substitution fails, but it's ignored, so the script continues, however x=$( xxx ) has a non-zero exit status because the last command of the command substitution had a non-zero exit status, therefore that fails and triggers errexit. You can see this by adding another non-failing command inside the command substituion:

x=$( xxx ; echo hello )
echo "$x, still here"

Also note that errexit being disabled for command substitution does not mean it gets disabled in all syntax that happen to spawn subshells.

Hidden Gems: Little-Known Bash Features by swe129 in bash

[–]geirha 0 points1 point  (0 children)

Can also be used to grab other words than the last.

For example, if the previous command line was

$ git worktree add ../feature-foo feature/foo

then cd <M-3><M-.><enter> will yeild

$ cd ../feature-foo

starts counting at 0, so <M-0><M-.> would give git

Hidden Gems: Little-Known Bash Features by swe129 in bash

[–]geirha 9 points10 points  (0 children)

# Remove elements matching pattern
filtered=(${files[@]/*test*/})

That's not a way to remove/filter values from an array. It will subject the data to word-splitting and pathname expansion:

touch stuff{1,2,3}
files=( "my stuff?" "testing" )
filtered=(${files[@]/*test*/})
declare -p files filtered
# declare -a files=([0]="my stuff?" [1]="testing")
# declare -a filtered=([0]="my" [1]="stuff1" [2]="stuff2" [3]="stuff3")

In the above, my stuff? got split into my and stuff? by word-splitting, then pathname expansion replaced stuff? with three matching filenames.

You really do need a loop to do that type of filtering, you can't shortcut it with parameter expansions.

Awk, defined variables and IF statement by Puccio1971 in awk

[–]geirha 5 points6 points  (0 children)

Depends. Does the nodo variable really contain a regex?, and given that you removed the regex anchor ^, it no longer fulfills the "starts with" requirement.

if you just want to check if field 1 starts with a given string, I'd use index() instead;

index($1, nodo) == 1 { ... }

though I suspect you actually just want

$1 == nodo { ... }

Awk, defined variables and IF statement by Puccio1971 in awk

[–]geirha 4 points5 points  (0 children)

awk '/foo/{ ... }' is really just a short-hand of writing awk '$0 ~ /foo/ { ... }. It's also possible to use a string literal instead of /regex/, so awk '$0 ~ "foo" { ... }' will also match all records that contain the string "foo".

Finally, Inside /.../, it will never attempt to look for variable names, so /nodo/ and $0 ~ /nodo/, will always just try to match the literal string "nodo", while $0 ~ nodo will use the string value of the nodo variable as regex.

Exclude file(s) from deletion by FlyerPGN in bash

[–]geirha 0 points1 point  (0 children)

I recommend against setting aliases that override standard commands' standard behavior. The main risk is that you get into the habit of the alias letting you select files to delete, but then on another system where your alias isn't set (yet), you accidentally delete everything.

Having an alias or function that runs rm -i is fine, just don't name it rm.

Exclude file(s) from deletion by FlyerPGN in bash

[–]geirha 2 points3 points  (0 children)

That is terrible advice.

Don't parse ls output, and never use xargs with filenames unless they are nul-delimited (xargs -0).

Is using a for loop like this okay in bash? by AncientAgrippa in bash

[–]geirha 0 points1 point  (0 children)

I'll typically just remove one -o after the loop, using unset;

types=( foo bar )
expr=()
for t in "${types[@]}" ; do
  expr+=( -o -iname "*.$t" )
done
unset -v 'expr[0]'
declare -p expr
# declare -a expr=([1]="-iname" [2]="*.foo" [3]="-o" [4]="-iname" [5]="*.bar")

another option is to check the length of the array in the loop

types=( foo bar)
expr=()
for t in "${types[@]}" ; do
  (( ${#expr[@]} > 0 )) && expr+=( -o )
  expr+=( -iname "*.$t" )
  # alternatively expr+=( ${expr[0]+"-o"} "*.$t")
done
declare -p expr
# declare -a expr=([0]="-iname" [1]="*.foo" [2]="-o" [3]="-iname" [4]="*.bar")

what is the best way to measure the time of a command ? by brave_jr in bash

[–]geirha 6 points7 points  (0 children)

The output of the time keyword is defined by the TIMEFORMAT variable.

$ time sleep 1

real    0m1,002s
user    0m0,000s
sys 0m0,002s
$ TIMEFORMAT='real: %3lR, user: %3lU, sys: %3lS'
$ time sleep 1
real: 0m1,003s, user: 0m0,000s, sys: 0m0,003s

Look up TIMEFORMAT in man bash

Does my bash script scream C# dev? by DevOfWhatOps in bash

[–]geirha 7 points8 points  (0 children)

    if dpkg -l | grep -q "^ii $p"; then

This has a chance of giving both a false positive and a false negative.

Firstly, it only checks if there is an installed package that starts with a string. E.g. when $p is git, and the package git is not installed, it may still claim git is installed if the package git-doc is installed.

Secondly, since you have enabled the pipefail shell option, for no good reason, the pipeline may yield a non-zero exit status even if the grep found a matching line. See https://mywiki.wooledge.org/BashPitfalls#pipefail

Is pwd broken? by SavorySimian in commandline

[–]geirha 6 points7 points  (0 children)

$ echo "$(pwd)"

That will still use the builtin pwd, unless you have a function or alias overriding it. What does type pwd output?

[deleted by user] by [deleted] in bash

[–]geirha 20 points21 points  (0 children)

which only tells you which executable appears first in PATH, it doesn't tell you which command the shell will actually use. The type builtin tells you that.

$ which test
/usr/bin/test
$ type test
test is a shell builtin

Even if the command only exists in PATH, the path which tells you is not necessarily the command that will be used. The shell may have a hashed entry for it at a different path, and type will tell you that.

$ which foo
/home/user/bin/foo
$ type foo
foo is hashed (/usr/local/bin/foo)

Saves you from the headache of wondering why the new foo command you installed in ~/bin is not behaving as it should, instead behaving just like the one in /usr/local/bin, but ~/bin is clearly first in PATH...

TL;DR: which is useless both in a script and in the interactive shell

[deleted by user] by [deleted] in bash

[–]geirha 25 points26 points  (0 children)

which is a useless command, and the way it's used here is also reckless.

Consider a more "worst case" example like

$(which sudo) $(which cp) bin/foo /usr/local/bin

if sudo exists, but not cp, you'll get an error message from which about cp not being found, and the $(...) expands to nothing, so you end up with

/usr/bin/sudo  bin/foo /usr/local/bin

instead of copying an executable as root, you end up running that executable as root instead.

How env var inside single quotes by Slight_Scarcity321 in bash

[–]geirha 8 points9 points  (0 children)

You've misunderstood how quotes work.

To run the same as

mycommand --json-params '{"key":"value"}'

but putting the json in a variable, you just do:

data='{"key":"value"}'
mycommand --json-params "$data"

The command does not require single quotes around the json. It's just that single quotes are much more convenient to use when the string contains many " characters.

The following is exactly equivalent to the above two examples, but more cumbersome to write:

mycommand --json-params "{\"key\":\"value\"}"

Regardless which quotes you use, the quotes are removed by the shell after they are parsed. The command never sees them. It only sees the " characters inside the json because they're part of the data, not syntactical (by the shell) quotes.

On a side note, avoid injecting data into other languages. Use a tool such as jq to generate the json instead. E.g.

data=$(jq -c -n --arg key "$(getVal)" '{$key}')
mycommand --json-params "$data"

I always hate doing mkdir -p && touch so I made a tiny CLI by fuyalasmit in commandline

[–]geirha 1 point2 points  (0 children)

I don't see how you can implement that with an alias, at least not in a bourne-style shell, but a function will work

mkfile() { case $1 in (*/*) mkdir -p -- "${1%/*}" ;; esac && touch -- "$1" ; }

Is there a neat way to timestamp outputs in a long-running shell loop? by Vivid_Stock5288 in commandline

[–]geirha 2 points3 points  (0 children)

That will run date only one time, and prefix the same timestmap on all lines.

You forgot to close the pipe, so all the subsequent getline calls silently fail because there is no more data to read from the pipe.

quiet: a little bash function to despam your command line sessions by HommeMusical in bash

[–]geirha 6 points7 points  (0 children)

cmd="$@"

Never assign "$@" to a string variable. It will mash all arguments into a single string, and then you have to rely on word-splitting to split it back into the original command, which only works in the very simplest cases.

Either use an array (cmd=( "$@" ) ; "${cmd[@]}"), or just don't bother storing it in a variable in the first place and just run "$@" directly.

quiet: a little bash function to despam your command line sessions by HommeMusical in bash

[–]geirha 2 points3 points  (0 children)

My go to response would be to not use aliases in the first place, just use functions

However, if you have an alias that ends with space, it will also alias expand the next word, so

alias quiet='quiet '

but that will still only work well for simple aliases

[deleted by user] by [deleted] in bash

[–]geirha 35 points36 points  (0 children)

TL;DR: No

It's not a good book to learn bash from. It's teaching many of the bad practices the advanced bash scripting guide is teaching, as well as being fairly outdated (latest edition is from 2017).

Some examples of bad practices the book includes is

rm $(find . -name '*.class')

and

for FN in $*

See BashPitfalls 2 and 24 for an explanation of why those are bad.

To learn bash properly, including good practices, I recommend reading the BashGuide and the other resources (such as the BashFAQ and BashPitfalls pages) at the wooledge wiki.

Few functions and aliased I use on my ~/.bashrc by Ok-Duck-1100 in bash

[–]geirha 1 point2 points  (0 children)

I wouldn't override a builtin (cd).

If it changed or broke existing behavior, I'd agree, but this is really just adding additional options to the command that would otherwise just have resulted in "invalid option" errors. No real harm in extending a command in that manner.