in Bash, how can I remove a numeric range of directories? - bash

I am writing an alias to remove a range of directories that contain integers. I can't figure out how to replace the values e.g. 322 and 394 with 2 variables (arguments) that I add when entering the command.
This is the alias in its current state.
alias rRange='ls -1 | awk -F'"'"'v'"'"' '"'"'{if ( ($2>=322) && ($2<=394) ) print "rm -fRv " $0 }'"'"''
but I would like to be able to enter:
rRange 322 394
to be able to drive that alias instead. Currently those values are hard coded in there.

A step by step, bottom-up, deconstruction on how to solve this and many similar problems:
To generate an integer sequence, use bash brace-expansion (see details in man bash):
$ echo {2..5}
2 3 4 5
You may also generate non-consecutive and multiple ranges:
$ echo {2..4} {8..9}
2 3 4 8 9
If you have variables instead of constants, you can use eval to expand them:
$ a=2 b=5
$ eval echo {$a..$b}
2 3 4 5
To list any file/directory which contains these values, enclose with *s:
$ eval echo *{$a..$b}*
To remove the files instead of listing them, use rm instead of echo:
$ eval rm *{$a..$b}*
To remove directories use rm -r (if directories are non-empty) or rmdir (if-empty):
$ eval rm -r *{$a..$b}*
$ eval rmdir *{$a..$b}*

Use a function instead of an alias:
function rRange {
for (( I = $1; I <=$2; ++I )); do
[[ -d $I ]] && rm -fRv "$I"
done
}

More expansion.
rm ... *{322..394}*

Related

What does nested parenthese mean in bash? [duplicate]

I am confused by the usage of brackets, parentheses, curly braces in Bash, as well as the difference between their double or single forms. Is there a clear explanation?
In Bash, test and [ are shell builtins.
The double bracket, which is a shell keyword, enables additional functionality. For example, you can use && and || instead of -a and -o and there's a regular expression matching operator =~.
Also, in a simple test, double square brackets seem to evaluate quite a lot quicker than single ones.
$ time for ((i=0; i<10000000; i++)); do [[ "$i" = 1000 ]]; done
real 0m24.548s
user 0m24.337s
sys 0m0.036s
$ time for ((i=0; i<10000000; i++)); do [ "$i" = 1000 ]; done
real 0m33.478s
user 0m33.478s
sys 0m0.000s
The braces, in addition to delimiting a variable name are used for parameter expansion so you can do things like:
Truncate the contents of a variable
$ var="abcde"; echo ${var%d*}
abc
Make substitutions similar to sed
$ var="abcde"; echo ${var/de/12}
abc12
Use a default value
$ default="hello"; unset var; echo ${var:-$default}
hello
and several more
Also, brace expansions create lists of strings which are typically iterated over in loops:
$ echo f{oo,ee,a}d
food feed fad
$ mv error.log{,.OLD}
(error.log is renamed to error.log.OLD because the brace expression
expands to "mv error.log error.log.OLD")
$ for num in {000..2}; do echo "$num"; done
000
001
002
$ echo {00..8..2}
00 02 04 06 08
$ echo {D..T..4}
D H L P T
Note that the leading zero and increment features weren't available before Bash 4.
Thanks to gboffi for reminding me about brace expansions.
Double parentheses are used for arithmetic operations:
((a++))
((meaning = 42))
for ((i=0; i<10; i++))
echo $((a + b + (14 * c)))
and they enable you to omit the dollar signs on integer and array variables and include spaces around operators for readability.
Single brackets are also used for array indices:
array[4]="hello"
element=${array[index]}
Curly brace are required for (most/all?) array references on the right hand side.
ephemient's comment reminded me that parentheses are also used for subshells. And that they are used to create arrays.
array=(1 2 3)
echo ${array[1]}
2
A single bracket ([) usually actually calls a program named [; man test or man [ for more info. Example:
$ VARIABLE=abcdef
$ if [ $VARIABLE == abcdef ] ; then echo yes ; else echo no ; fi
yes
The double bracket ([[) does the same thing (basically) as a single bracket, but is a bash builtin.
$ VARIABLE=abcdef
$ if [[ $VARIABLE == 123456 ]] ; then echo yes ; else echo no ; fi
no
Parentheses (()) are used to create a subshell. For example:
$ pwd
/home/user
$ (cd /tmp; pwd)
/tmp
$ pwd
/home/user
As you can see, the subshell allowed you to perform operations without affecting the environment of the current shell.
(a) Braces ({}) are used to unambiguously identify variables. Example:
$ VARIABLE=abcdef
$ echo Variable: $VARIABLE
Variable: abcdef
$ echo Variable: $VARIABLE123456
Variable:
$ echo Variable: ${VARIABLE}123456
Variable: abcdef123456
(b) Braces are also used to execute a sequence of commands in the current shell context, e.g.
$ { date; top -b -n1 | head ; } >logfile
# 'date' and 'top' output are concatenated,
# could be useful sometimes to hunt for a top loader )
$ { date; make 2>&1; date; } | tee logfile
# now we can calculate the duration of a build from the logfile
There is a subtle syntactic difference with ( ), though (see bash reference) ; essentially, a semicolon ; after the last command within braces is a must, and the braces {, } must be surrounded by spaces.
Brackets
if [ CONDITION ] Test construct
if [[ CONDITION ]] Extended test construct
Array[1]=element1 Array initialization
[a-z] Range of characters within a Regular Expression
$[ expression ] A non-standard & obsolete version of $(( expression )) [1]
[1] http://wiki.bash-hackers.org/scripting/obsolete
Curly Braces
${variable} Parameter substitution
${!variable} Indirect variable reference
{ command1; command2; . . . commandN; } Block of code
{string1,string2,string3,...} Brace expansion
{a..z} Extended brace expansion
{} Text replacement, after find and xargs
Parentheses
( command1; command2 ) Command group executed within a subshell
Array=(element1 element2 element3) Array initialization
result=$(COMMAND) Command substitution, new style
>(COMMAND) Process substitution
<(COMMAND) Process substitution
Double Parentheses
(( var = 78 )) Integer arithmetic
var=$(( 20 + 5 )) Integer arithmetic, with variable assignment
(( var++ )) C-style variable increment
(( var-- )) C-style variable decrement
(( var0 = var1<98?9:21 )) C-style ternary operation
I just wanted to add these from TLDP:
~:$ echo $SHELL
/bin/bash
~:$ echo ${#SHELL}
9
~:$ ARRAY=(one two three)
~:$ echo ${#ARRAY}
3
~:$ echo ${TEST:-test}
test
~:$ echo $TEST
~:$ export TEST=a_string
~:$ echo ${TEST:-test}
a_string
~:$ echo ${TEST2:-$TEST}
a_string
~:$ echo $TEST2
~:$ echo ${TEST2:=$TEST}
a_string
~:$ echo $TEST2
a_string
~:$ export STRING="thisisaverylongname"
~:$ echo ${STRING:4}
isaverylongname
~:$ echo ${STRING:6:5}
avery
~:$ echo ${ARRAY[*]}
one two one three one four
~:$ echo ${ARRAY[*]#one}
two three four
~:$ echo ${ARRAY[*]#t}
one wo one hree one four
~:$ echo ${ARRAY[*]#t*}
one wo one hree one four
~:$ echo ${ARRAY[*]##t*}
one one one four
~:$ echo $STRING
thisisaverylongname
~:$ echo ${STRING%name}
thisisaverylong
~:$ echo ${STRING/name/string}
thisisaverylongstring
The difference between test, [ and [[ is explained in great details in the BashFAQ.
(Note: The link shows many examples for comparison)
To cut a long story short: test implements the old, portable syntax of
the command. In almost all shells (the oldest Bourne shells are the
exception), [ is a synonym for test (but requires a final argument of
]). Although all modern shells have built-in implementations of [,
there usually still is an external executable of that name, e.g.
/bin/[.
[[ is a new, improved version of it, and it is a keyword, not a program.
This has beneficial effects on the ease of use, as shown below. [[ is
understood by KornShell and BASH (e.g. 2.03), but not by the older
POSIX or BourneShell.
And the conclusion:
When should the new test command [[ be used, and when the old one [?
If portability/conformance to POSIX or the BourneShell is a concern, the old syntax should
be used. If on the other hand the script requires BASH, Zsh, or KornShell,
the new syntax is usually more flexible.
Parentheses in function definition
Parentheses () are being used in function definition:
function_name () { command1 ; command2 ; }
That is the reason you have to escape parentheses even in command parameters:
$ echo (
bash: syntax error near unexpected token `newline'
$ echo \(
(
$ echo () { command echo The command echo was redefined. ; }
$ echo anything
The command echo was redefined.
Some common and handy uses for brackets, parenthesis, and braces
As mentioned above, sometimes you want a message displayed without losing the return value. This is a handy snippet:
$ [ -f go.mod ] || { echo 'File not found' && false; }
This produced no output and a 0 (true) return value if the file go.mod exists in the current directory. Test the result:
$ echo $?
0
If the file does not exist, you get the message but also a return value of 1 (false), which can also be tested:
$ [ -f fake_file ] || { echo 'File not found'; false; }
File not found
$ echo $?
1
You can also simply create a function to check if a file exists:
fileexists() { [ -f "$1" ]; }
or if a file is readable (not corrupted, have permissions, etc.):
canread() { [ -r "$1" ]; }
or if it is a directory:
isdir() { [ -d "$1" ]; }
or is writable for the current user:
canwrite() { [ -w "$1" ]; }
or if a file exists and is not empty (like a log file with content...)
isempty() { [ -s "$1" ]; }
There are more details at: TLDP
You can also see if a program exists and is available on the path:
exists () { command -v $1 > /dev/null 2>&1; }
This is useful in scripts, for example:
# gitit does an autosave commit to the current
# if Git is installed and available.
# If git is not available, it will use brew
# (on macOS) to install it.
#
# The first argument passed, if any, is used as
# the commit message; otherwise the default is used.
gitit() {
$(exists git) && {
git add --all;
git commit -m "${1:-'GitBot: dev progress autosave'}";
git push;
} || brew install git;
}
Additional info about How to use parentheses to group and expand expressions:
(it is listed on the link syntax-brackets)
Some main points in there:
Group commands in a sub-shell: ( )
(list)
Group commands in the current shell: { }
{ list; }
Test - return the binary result of an expression: [[ ]]
[[ expression ]]
Arithmetic expansion
The format for Arithmetic expansion is:
$(( expression ))
The format for a simple Arithmetic Evaluation is:
(( expression ))
Combine multiple expressions
( expression )
(( expr1 && expr2 ))
Truncate the contents of a variable
$ var="abcde"; echo ${var%d*}
abc
Make substitutions similar to sed
$ var="abcde"; echo ${var/de/12}
abc12
Use a default value
$ default="hello"; unset var; echo ${var:-$default}
hello

Bash/GitBash syntax for multiple variable value assignment not working when value is a command's output

I have GitforWindows 2.20.1 installed in Windows 7 64Bit.
Now as I stated in the question, the multiple variable assignment syntax is not always working, especially when the value to be assigned is a command's output, i.e.:
read -r a b c <<< $(echo 1 2 3) ; echo "$a|$b|$c"
works, but these doesn't:
read -r SCRIPTDIR MAGE2ROOT <<< $(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
## OR ##
read -r -d "\n" SCRIPTDIR MAGE2ROOT <<< $(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
cause if I print the output just after this like:
echo $SCRIPTDIR && echo $MAGE2ROOT && exit
it just prints the path once. Why? And how can we make it work ?
Any help/guidance required.
read doesn't do what you want it to. If it doesn't read as many "words" as there are variables to assign, it just leaves the extra variables blank. In this case, it's only getting one word (the path to the script's directory), but it has two variables to assign (SCRIPTDIR and MAGE2ROOT), so it sets SCRIPTDIR to the path, and MAGE2ROOT is left blank. Try your first example, but without supplying enough values to fill in all the variables:
$ read -r a b c <<< $(echo 1 2 3) ; echo "$a|$b|$c"
1|2|3
$ read -r a b c <<< $(echo 1 2) ; echo "$a|$b|$c"
1|2|
$ read -r a b c <<< $(echo 1) ; echo "$a|$b|$c"
1||
Fortunately, this is easy to solve (as #OlliK said) with MAGE2ROOT="$SCRIPTDIR". Actually, there's no point in using read at all, just use a plain assignment:
SCRIPTDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
MAGE2ROOT="$SCRIPTDIR"

How can I extract the last X folders from the path in k shell

Say I have a path name /server/user/folderA/folderB/folderC, how would I extract (to a variable) just the last few folders? I'm looking for something that will be flexible enough to give me folderC, or folderB/folderC, or folderA/folderB/folderC, etc.
I'm trying to use sed, but I'm not sure that's the best approach.
This would have to be in either ksh or csh (no bash on our machines, sadly)
This will get you started:
arr=( $(echo "/server/user/folderA/folderB/folderC" | sed 's#/# #g') )
echo ${#arr[*]}
echo ${arr[*]}
echo ${arr[3]}
echo "${arr[2]}/${arr[3]}/${arr[4]}"
output
5
server user folderA folderB folderC
folderB
folderA/folderB/folderC
IHTH
You can use arrays, but ksh88 (at least the one I tested with, on Solaris 8) uses the old Korn Shell syntax of set -A, and it doesn’t do (( i++ )) either, so this looks a bit more baroque than contemporary ksh93 or mksh code. On the other hand, I’m also giving you a function to extract the last n items ;)
p=/server/user/folderA/folderB/folderC
saveIFS=$IFS
IFS=/
set -A fullpath -- $p
echo all: "${fullpath[*]}"
unset fullpath[0] # leading slash
unset fullpath[1]
unset fullpath[2]
echo all but first two: "${fullpath[*]}"
IFS=$saveIFS
# example function to get the last n:
function pathlast {
typeset saveIFS parr i=0 n
saveIFS=$IFS
IFS=/
set -A parr -- $2
(( n = ${#parr[*]} - $1 ))
while (( i < n )); do
unset parr[i]
(( i += 1 ))
done
echo "${parr[*]}"
IFS=$saveIFS
}
for lst in 1 2 3; do
echo all but last $lst: $(pathlast $lst "$p")
done
Output:
tg#stinky:~ $ /bin/ksh x
all: /server/user/folderA/folderB/folderC
all but first two: folderA/folderB/folderC
all but last 1: folderC
all but last 2: folderB/folderC
all but last 3: folderA/folderB/folderC
Other than the first line setting $p, you can just copy the function part.
This could be done with perl if you've got it:
$ path=/server/user/folderA/folderB/folderC
$ X=3
$ echo $path|perl -F/ -ane '{print join "/",#F[(#F-'$X')..(#F-1)]}'
folderA/folderB/folderC

How to delete all but two instances of a file?

I have a directory with similarly named files, in this pattern:
00002_930831_fa.ppm 00398_940422_fa.ppm 00714_960530_fa.ppm
00002_930831_fb.ppm 00398_940422_fb.ppm 00714_960530_fb.ppm
00002_931230_fa.ppm 00399_940422_fa.ppm 00714_960620_fa.ppm
00002_931230_fb.ppm 00399_940422_fb.ppm 00714_960620_fb.ppm
00002_940128_fa.ppm 00400_940422_fa.ppm 00715_941201_fa.ppm
00002_940128_fb.ppm 00400_940422_fb.ppm 00715_941201_fb.ppm
00002_940422_fa.ppm 00401_940422_fa.ppm 00715_941205_fa.ppm
00002_940422_fb.ppm 00401_940422_fb.ppm 00715_941205_fb.ppm
00002_940928_fa.ppm 00402_940422_fa.ppm 00716_941201_fa.ppm
00002_940928_fb.ppm 00402_940422_fb.ppm 00716_941201_fb.ppm
What I need to do is remove for example all but two instances of the 00002 sample (doesn't matter which ones), so that I'm left for example with 00002_930831_fa.ppm and 00002_930831_fb.ppm. The problem is I need this done for all samples, 00003, 00004 and so on. I need to be left with two files for each sample.
I've tried with find but I'm not sure how to frase my condition.
Can this be solved by simply piping commands or do I have to solve it with a bash script?
Just use head or tail to filter your filename list:
ls 00002_* | tail -n +3 | xargs rm
Create an array that contains all matching file names, then use the substring parameter expansion operator to pass all but the first two elements as arguments to rm.
while read -r sample; do
matching_files=( ${sample}_* )
# To make sure at least two files survive:
(( ${#matching_files[#]} > 2 )) && rm "${matching_files[#]:2}"
done < samples.txt
Using an associative array:
#!/bin/bash
[[ BASH_VERSINFO -ge 4 ]] || {
echo "You need Bash 4.0 or newer to run this script." >&2
exit 1
}
declare -A COUNTER=()
for A in *.ppm; do
IFS=_ read I __ <<< "$A"
(( ++COUNTER[$I] > 2 )) && rm "$A"
done
Simulation:
Skip 00002_930831_fa.ppm
Skip 00002_930831_fb.ppm
rm 00002_931230_fa.ppm
rm 00002_931230_fb.ppm
rm 00002_940128_fa.ppm
rm 00002_940128_fb.ppm
rm 00002_940422_fa.ppm
rm 00002_940422_fb.ppm
rm 00002_940928_fa.ppm
rm 00002_940928_fb.ppm
Skip 00398_940422_fa.ppm
Skip 00398_940422_fb.ppm
Skip 00399_940422_fa.ppm
Skip 00399_940422_fb.ppm
Skip 00400_940422_fa.ppm
Skip 00400_940422_fb.ppm
Skip 00401_940422_fa.ppm
Skip 00401_940422_fb.ppm
Skip 00402_940422_fa.ppm
Skip 00402_940422_fb.ppm
Skip 00714_960530_fa.ppm
Skip 00714_960530_fb.ppm
rm 00714_960620_fa.ppm
rm 00714_960620_fb.ppm
Skip 00715_941201_fa.ppm
Skip 00715_941201_fb.ppm
rm 00715_941205_fa.ppm
rm 00715_941205_fb.ppm
Skip 00716_941201_fa.ppm
Skip 00716_941201_fb.ppm
Note: Test it first on some dummy files.
Come to think of it:
IFS=_ read I __ <<< "$A"
Can just be
I=${A%%_*}
with bash version 4:
declare -A files
for f in *ppm; do
files[${f%%_*}]+="$f "
done
for i in "${!files[#]}"; do
set -- ${files[$i]}
shift 2
(($# > 0)) && echo rm $*
done
Remove echo if you're satisfied it's selecting the right files to delete.
Won't work if there are any filenames with whitespace.

How to use double or single brackets, parentheses, curly braces

I am confused by the usage of brackets, parentheses, curly braces in Bash, as well as the difference between their double or single forms. Is there a clear explanation?
In Bash, test and [ are shell builtins.
The double bracket, which is a shell keyword, enables additional functionality. For example, you can use && and || instead of -a and -o and there's a regular expression matching operator =~.
Also, in a simple test, double square brackets seem to evaluate quite a lot quicker than single ones.
$ time for ((i=0; i<10000000; i++)); do [[ "$i" = 1000 ]]; done
real 0m24.548s
user 0m24.337s
sys 0m0.036s
$ time for ((i=0; i<10000000; i++)); do [ "$i" = 1000 ]; done
real 0m33.478s
user 0m33.478s
sys 0m0.000s
The braces, in addition to delimiting a variable name are used for parameter expansion so you can do things like:
Truncate the contents of a variable
$ var="abcde"; echo ${var%d*}
abc
Make substitutions similar to sed
$ var="abcde"; echo ${var/de/12}
abc12
Use a default value
$ default="hello"; unset var; echo ${var:-$default}
hello
and several more
Also, brace expansions create lists of strings which are typically iterated over in loops:
$ echo f{oo,ee,a}d
food feed fad
$ mv error.log{,.OLD}
(error.log is renamed to error.log.OLD because the brace expression
expands to "mv error.log error.log.OLD")
$ for num in {000..2}; do echo "$num"; done
000
001
002
$ echo {00..8..2}
00 02 04 06 08
$ echo {D..T..4}
D H L P T
Note that the leading zero and increment features weren't available before Bash 4.
Thanks to gboffi for reminding me about brace expansions.
Double parentheses are used for arithmetic operations:
((a++))
((meaning = 42))
for ((i=0; i<10; i++))
echo $((a + b + (14 * c)))
and they enable you to omit the dollar signs on integer and array variables and include spaces around operators for readability.
Single brackets are also used for array indices:
array[4]="hello"
element=${array[index]}
Curly brace are required for (most/all?) array references on the right hand side.
ephemient's comment reminded me that parentheses are also used for subshells. And that they are used to create arrays.
array=(1 2 3)
echo ${array[1]}
2
A single bracket ([) usually actually calls a program named [; man test or man [ for more info. Example:
$ VARIABLE=abcdef
$ if [ $VARIABLE == abcdef ] ; then echo yes ; else echo no ; fi
yes
The double bracket ([[) does the same thing (basically) as a single bracket, but is a bash builtin.
$ VARIABLE=abcdef
$ if [[ $VARIABLE == 123456 ]] ; then echo yes ; else echo no ; fi
no
Parentheses (()) are used to create a subshell. For example:
$ pwd
/home/user
$ (cd /tmp; pwd)
/tmp
$ pwd
/home/user
As you can see, the subshell allowed you to perform operations without affecting the environment of the current shell.
(a) Braces ({}) are used to unambiguously identify variables. Example:
$ VARIABLE=abcdef
$ echo Variable: $VARIABLE
Variable: abcdef
$ echo Variable: $VARIABLE123456
Variable:
$ echo Variable: ${VARIABLE}123456
Variable: abcdef123456
(b) Braces are also used to execute a sequence of commands in the current shell context, e.g.
$ { date; top -b -n1 | head ; } >logfile
# 'date' and 'top' output are concatenated,
# could be useful sometimes to hunt for a top loader )
$ { date; make 2>&1; date; } | tee logfile
# now we can calculate the duration of a build from the logfile
There is a subtle syntactic difference with ( ), though (see bash reference) ; essentially, a semicolon ; after the last command within braces is a must, and the braces {, } must be surrounded by spaces.
Brackets
if [ CONDITION ] Test construct
if [[ CONDITION ]] Extended test construct
Array[1]=element1 Array initialization
[a-z] Range of characters within a Regular Expression
$[ expression ] A non-standard & obsolete version of $(( expression )) [1]
[1] http://wiki.bash-hackers.org/scripting/obsolete
Curly Braces
${variable} Parameter substitution
${!variable} Indirect variable reference
{ command1; command2; . . . commandN; } Block of code
{string1,string2,string3,...} Brace expansion
{a..z} Extended brace expansion
{} Text replacement, after find and xargs
Parentheses
( command1; command2 ) Command group executed within a subshell
Array=(element1 element2 element3) Array initialization
result=$(COMMAND) Command substitution, new style
>(COMMAND) Process substitution
<(COMMAND) Process substitution
Double Parentheses
(( var = 78 )) Integer arithmetic
var=$(( 20 + 5 )) Integer arithmetic, with variable assignment
(( var++ )) C-style variable increment
(( var-- )) C-style variable decrement
(( var0 = var1<98?9:21 )) C-style ternary operation
I just wanted to add these from TLDP:
~:$ echo $SHELL
/bin/bash
~:$ echo ${#SHELL}
9
~:$ ARRAY=(one two three)
~:$ echo ${#ARRAY}
3
~:$ echo ${TEST:-test}
test
~:$ echo $TEST
~:$ export TEST=a_string
~:$ echo ${TEST:-test}
a_string
~:$ echo ${TEST2:-$TEST}
a_string
~:$ echo $TEST2
~:$ echo ${TEST2:=$TEST}
a_string
~:$ echo $TEST2
a_string
~:$ export STRING="thisisaverylongname"
~:$ echo ${STRING:4}
isaverylongname
~:$ echo ${STRING:6:5}
avery
~:$ echo ${ARRAY[*]}
one two one three one four
~:$ echo ${ARRAY[*]#one}
two three four
~:$ echo ${ARRAY[*]#t}
one wo one hree one four
~:$ echo ${ARRAY[*]#t*}
one wo one hree one four
~:$ echo ${ARRAY[*]##t*}
one one one four
~:$ echo $STRING
thisisaverylongname
~:$ echo ${STRING%name}
thisisaverylong
~:$ echo ${STRING/name/string}
thisisaverylongstring
The difference between test, [ and [[ is explained in great details in the BashFAQ.
(Note: The link shows many examples for comparison)
To cut a long story short: test implements the old, portable syntax of
the command. In almost all shells (the oldest Bourne shells are the
exception), [ is a synonym for test (but requires a final argument of
]). Although all modern shells have built-in implementations of [,
there usually still is an external executable of that name, e.g.
/bin/[.
[[ is a new, improved version of it, and it is a keyword, not a program.
This has beneficial effects on the ease of use, as shown below. [[ is
understood by KornShell and BASH (e.g. 2.03), but not by the older
POSIX or BourneShell.
And the conclusion:
When should the new test command [[ be used, and when the old one [?
If portability/conformance to POSIX or the BourneShell is a concern, the old syntax should
be used. If on the other hand the script requires BASH, Zsh, or KornShell,
the new syntax is usually more flexible.
Parentheses in function definition
Parentheses () are being used in function definition:
function_name () { command1 ; command2 ; }
That is the reason you have to escape parentheses even in command parameters:
$ echo (
bash: syntax error near unexpected token `newline'
$ echo \(
(
$ echo () { command echo The command echo was redefined. ; }
$ echo anything
The command echo was redefined.
Some common and handy uses for brackets, parenthesis, and braces
As mentioned above, sometimes you want a message displayed without losing the return value. This is a handy snippet:
$ [ -f go.mod ] || { echo 'File not found' && false; }
This produced no output and a 0 (true) return value if the file go.mod exists in the current directory. Test the result:
$ echo $?
0
If the file does not exist, you get the message but also a return value of 1 (false), which can also be tested:
$ [ -f fake_file ] || { echo 'File not found'; false; }
File not found
$ echo $?
1
You can also simply create a function to check if a file exists:
fileexists() { [ -f "$1" ]; }
or if a file is readable (not corrupted, have permissions, etc.):
canread() { [ -r "$1" ]; }
or if it is a directory:
isdir() { [ -d "$1" ]; }
or is writable for the current user:
canwrite() { [ -w "$1" ]; }
or if a file exists and is not empty (like a log file with content...)
isempty() { [ -s "$1" ]; }
There are more details at: TLDP
You can also see if a program exists and is available on the path:
exists () { command -v $1 > /dev/null 2>&1; }
This is useful in scripts, for example:
# gitit does an autosave commit to the current
# if Git is installed and available.
# If git is not available, it will use brew
# (on macOS) to install it.
#
# The first argument passed, if any, is used as
# the commit message; otherwise the default is used.
gitit() {
$(exists git) && {
git add --all;
git commit -m "${1:-'GitBot: dev progress autosave'}";
git push;
} || brew install git;
}
Additional info about How to use parentheses to group and expand expressions:
(it is listed on the link syntax-brackets)
Some main points in there:
Group commands in a sub-shell: ( )
(list)
Group commands in the current shell: { }
{ list; }
Test - return the binary result of an expression: [[ ]]
[[ expression ]]
Arithmetic expansion
The format for Arithmetic expansion is:
$(( expression ))
The format for a simple Arithmetic Evaluation is:
(( expression ))
Combine multiple expressions
( expression )
(( expr1 && expr2 ))
Truncate the contents of a variable
$ var="abcde"; echo ${var%d*}
abc
Make substitutions similar to sed
$ var="abcde"; echo ${var/de/12}
abc12
Use a default value
$ default="hello"; unset var; echo ${var:-$default}
hello

Resources