Good advices for bash developer.

About writing shell scripts and making the most of your shell
Forum rules
Topics in this forum are automatically closed 6 months after creation.
1000
Level 6
Level 6
Posts: 1039
Joined: Wed Jul 29, 2020 2:14 am

Good advices for bash developer.

Post by 1000 »

Sometimes when I wrote script, something does not work as I assumed.

1. Read olny second field

Code: Select all

$ echo "yy uu" | cut -d' ' -f2
uu

$ echo "yy" | cut -d' ' -f2
yy
The same with awk

Code: Select all

$ echo "yy uu" | awk '{print $2}'
uu

$ echo "yy" | awk '{print $2}'

2. Count lines

Code: Select all

$ echo "K" | wc -l
1

$ echo "" | wc -l
1
We can remove empty lines https://stackoverflow.com/questions/164 ... -using-sed

Code: Select all

$ echo "K" | grep "\S" | wc -l
1
$ echo "" | grep "\S" | wc -l
0

Do you know any more examples?
Can you give or the write place where we can find it?
Last edited by LockBot on Wed Dec 28, 2022 7:16 am, edited 2 times in total.
Reason: Topic automatically closed 6 months after creation. New replies are no longer allowed.
Welcome
Level 6
Level 6
Posts: 1026
Joined: Wed Aug 19, 2020 11:38 am

Re: Good advices for bash developer.

Post by Welcome »

2. Count lines

Code: Select all

$ echo -n "K" | wc -l
0

$ echo -n "" | wc -l
0
I see in 'man wc' ...

Code: Select all

       -l, --lines
              print the newline counts
... so this must be the correct behavior.
Welcome
Level 6
Level 6
Posts: 1026
Joined: Wed Aug 19, 2020 11:38 am

Re: Good advices for bash developer.

Post by Welcome »

Here's a puzzle for you. Say I want a script file to create another script file.

I can easily do this:

Code: Select all

printf "#!/bin/bash\nzenity --password --title=\"Authenticate\"\n\n" > /tmp/passwordmagic
But, if I try this:

Code: Select all

echo "#!/bin/sh" > /tmp/passwordmagic
echo "zenity --password --title=\"Authenticate\"" >>  /tmp/passwordmagic
The first line gives an error:
bash: !/bin/sh: event not found
A work around that doesn't use printf might be:

Code: Select all

echo -n "#" > /tmp/passwordmagic
echo -n "!" >> /tmp/passwordmagic
echo "/bin/sh" >> /tmp/passwordmagic
echo "zenity --password --title=\"Authenticate\"" >>  /tmp/passwordmagic
Any idea why echo "#!/bin/sh" > /tmp/passwordmagic gives that error?
1000
Level 6
Level 6
Posts: 1039
Joined: Wed Jul 29, 2020 2:14 am

Re: Good advices for bash developer.

Post by 1000 »

Try this

Code: Select all

printf '#!/bin/bash\nzenity --password --title=\"Authenticate\"\n\n' > passwordmagic
Edit

Code: Select all

$ V="K" ; echo "$V"
K

$ V="K" ; echo '$V'
$V
Welcome
Level 6
Level 6
Posts: 1026
Joined: Wed Aug 19, 2020 11:38 am

Re: Good advices for bash developer.

Post by Welcome »

printf works. :D

Here's another echo solution:

Code: Select all

echo "#!""/bin/sh" > /tmp/passwordmagic
Edit: Using echo, the single quote works.

Code: Select all

echo '#!/bin/sh' > /tmp/passwordmagic 
The result:

Code: Select all

$ cat /tmp/passwordmagic
#!/bin/sh
The reason is that ! is used to call up the last command executed. ! is a special character to bash, it is used to refer to previous commands; eg,

Code: Select all

!rm
will recall and execute the last command that began with the string "rm", and

Code: Select all

!rm:p
will recall but not execute the last command that began with the string "rm". bash is interpreting the exclamation mark in echo "#!/bin/sh" as "substitute here the last command that began with the character(s) immediately following the exclamation mark", and grumbles at you that it cannot find an event (command) in your history that began with /bin/sh. ref: https://serverfault.com/questions/20826 ... -not-found

Code: Select all

$ V="K" ; echo "$V"
K

$ V="K" ; echo '$V'
$V
The double quote allows variable substitution. The single quote does not. :D
Last edited by Welcome on Mon Feb 15, 2021 11:47 am, edited 1 time in total.
1000
Level 6
Level 6
Posts: 1039
Joined: Wed Jul 29, 2020 2:14 am

Re: Good advices for bash developer.

Post by 1000 »

Line counting .

Code: Select all

$ K=$(echo -e "1\n2\n3\n4") ; echo $K | wc -l
1

$ K=$(echo -e "1\n2\n3\n4") ; echo "$K" | wc -l
4
Welcome
Level 6
Level 6
Posts: 1026
Joined: Wed Aug 19, 2020 11:38 am

Re: Good advices for bash developer.

Post by Welcome »

1000 wrote: Mon Feb 15, 2021 11:46 am Line counting .

Code: Select all

$ K=$(echo -e "1\n2\n3\n4") ; echo $K | wc -l
1

$ K=$(echo -e "1\n2\n3\n4") ; echo "$K" | wc -l
4
That's a good one!

Code: Select all

$ K=$(echo -e "1\n2\n3\n4") ; echo $K 
1 2 3 4
K=$(echo -e "1\n2\n3\n4") ; echo "$K"
1
2
3
4
My head is hurting from that one! :shock:
Welcome
Level 6
Level 6
Posts: 1026
Joined: Wed Aug 19, 2020 11:38 am

Re: Good advices for bash developer.

Post by Welcome »

The largest value returned by a Bash script function is 255.

What happens if the return value is greater than 255? What happens if you use a negative number as a return value?

Code: Select all

#!/bin/bash

# Global variable
number=0

function demo-return {
    number=$1
    # Return values up to 255
    return $1 
}

for ((x = 253; x <= 258; ++x)) ; do
    demo-return $x
    echo Input: $x, Global: $number, Output: $?
done

for ((x = -254; x >= -259; --x)) ; do
    demo-return $x
    echo Input: $x, Global: $number, Output: $?
done
Results:

Code: Select all

Input: 253, Global: 253, Output: 253
Input: 254, Global: 254, Output: 254
Input: 255, Global: 255, Output: 255
Input: 256, Global: 256, Output: 0
Input: 257, Global: 257, Output: 1
Input: 258, Global: 258, Output: 2
Input: -254, Global: -254, Output: 2
Input: -255, Global: -255, Output: 1
Input: -256, Global: -256, Output: 0
Input: -257, Global: -257, Output: 255
Input: -258, Global: -258, Output: 254
Input: -259, Global: -259, Output: 253
Using different values, you can see more of what is happening when attempting to return a negative number:

Code: Select all

Input: 0, Global: 0, Output: 0
Input: -1, Global: -1, Output: 255
Input: -2, Global: -2, Output: 254
Input: -3, Global: -3, Output: 253
Input: -4, Global: -4, Output: 252
Input: -5, Global: -5, Output: 251
Input: -6, Global: -6, Output: 250
1000
Level 6
Level 6
Posts: 1039
Joined: Wed Jul 29, 2020 2:14 am

Re: Good advices for bash developer.

Post by 1000 »

Maybe that's not the best example.
Because
- In the script "$0" returns the name of the script.
- In the terminal "$0" returns "bash" from /bin/bash
- The situation occurs when you used relative links in the script and you want to run the script from a different place than previously tested.
- So, this is more of a logical error.
But the example is intended to show the problem of "relative link" when you want use.

Code: Select all

$ cd /bin ; pwd ; MY_PATH=$(dirname $0) ; echo "MY_PATH=${MY_PATH}" ; ls -l ${MY_PATH}/lsblk ; cd .. ; ls -l ${MY_PATH}/lsblk ; cd /bin
/bin
MY_PATH=.
-rwxr-xr-x 1 root root 133352 lip 21  2020 ./lsblk
ls: cannot access to './lsblk': There is no such file or directory 
Better way

Code: Select all

$ cd /bin ; pwd  ; MY_PATH=$(dirname "$(which $0)") ; echo "MY_PATH=${MY_PATH}" ; ls -l ${MY_PATH}/lsblk ; cd .. ; ls -l ${MY_PATH}/lsblk ; cd /bin
/bin
MY_PATH=/bin
-rwxr-xr-x 1 root root 133352 lip 21  2020 /bin/lsblk
-rwxr-xr-x 1 root root 133352 lip 21  2020 /bin/lsblk
You can create absolute path.
But if you will move script in the same time, in the same place own script and his libraries. Then you will need change manually absolute path in script.
The "which $0" command creates an absolute path to where the script resides

Code: Select all

$ cd /bin ; pwd  ; MY_PATH=$(pwd) ; echo "MY_PATH=${MY_PATH}" ; ls -l ${MY_PATH}/lsblk ; cd .. ; ls -l ${MY_PATH}/lsblk ; cd /bin
/bin
MY_PATH=/bin
-rwxr-xr-x 1 root root 133352 lip 21  2020 /bin/lsblk
-rwxr-xr-x 1 root root 133352 lip 21  2020 /bin/lsblk
Welcome
Level 6
Level 6
Posts: 1026
Joined: Wed Aug 19, 2020 11:38 am

Re: Good advices for bash developer.

Post by Welcome »

Interesting use of "which". Thanks for the tip! I now see that the use of an absolute path is very important. :D

I created a small script named "which-sample" to demo "which" in simple terms. Depending on how I start the script, I get different results. This (I think) helps to highlight the importance of using an absolute path name.

which-sample:

Code: Select all

#!/bin/bash

echo $(which $0)
echo $(dirname "$(which $0)")

read x
If I start the script in a terminal with the full absolute path, I get these results:

Code: Select all

$ /tmp/which-sample
/tmp/which-sample
/tmp
But, if I start the script in a terminal with a relative path, I get these results:

Code: Select all

$ ./which-sample
./which-sample
.
So, it's really important to start the script using the full path. Thanks! :D
User avatar
Termy
Level 12
Level 12
Posts: 4248
Joined: Mon Sep 04, 2017 8:49 pm
Location: UK
Contact:

Re: Good advices for bash developer.

Post by Termy »

Lots. Most of my BASH and Bourne Shell programs and quick scripts are here. I also put out a bunch of stuff on YouTube, the link to which you'll see in my signature. Off-hand, though, BASH supports associative arrays, which are especially useful when wanting to uniq-ify, without bringing in another program like uniq(1) or sort(1). IE:

Code: Select all

#!/usr/bin/env bash
  
test() {
    declare -A Strings
    for String in "$@"; {
        Strings["$String"]=1
    }   
    
    for String in "${!Strings[@]}"; {
        printf '%s\n' "$String"
    }
}

test a series of of strings
Its output is:

Code: Select all

a
series
of
strings
Depending on the data you're parsing, it's far more efficient than using those other programs or using something like awk(1). As an added bonus, the duplicate lines don't have to be adjacent as they do with uniq(1) and sort(1), since it's just a series of unique keys.

Here's another cool one. You can give your for loops a sort of added dimension by having key=value pairs in the initial LIST part and accessing the variable by using a form of parameter expansion which removes a prefix or suffix. IE:

Code: Select all

for Person in\ 
    'Tender Knuckle':43\
    'Billy Bob Jr.':2\
    'Thomas (the) Tank':50
{
    printf "%s is %d years of age.\n" "${Person%:*}" "${Person##*:}"
}
But by far one of the best pieces of advice I could give you, is to read the bash(1) man page, properly.

Here's another, but this is the last one, lest I be here forever. People like to abuse cat(1) a lot, but it's typically not needed at all and is just inefficient. Instead, you can do it with BASH itself, using a while read loop. IE:

Code: Select all

while read; do
	printf '%s\n' "$REPLY"
done < ~/.bashrc
Especially useful for something like a heredoc (<<) or herestring (<<<) taking in command ($() or ``) or process (<()) substitution. It's doable in Bourne Shell as well, but sometimes needs extra tweaking for it to work properly. Why do it this way? Well, aside from being efficient, it's also incredibly flexible; you process each line yourself.

If you just want to display the contents of something like a file, without doing any special testing or parsing, you can run these two beauties:

Code: Select all

readarray Lines < ~/.bashrc
printf '%s' "${Lines[@]}"
You can also use that same array to determine how many lines are in the file, with:

Code: Select all

printf '%d\n' ${#Lines[@]}
There's rarely a real need to use all of the many programs people fill their scripts with, because BASH itself is far more capable than people realise. If you're handling a lot of data or need special features of certain programs and it's impractical or difficult to achieve with BASH, then fair enough, but most of the time, when I see people using those programs, they're using them unnecessarily. As great as those tools can be, they can also be a crutch.

Okay, now I'm done. :lol:
Last edited by Termy on Tue Dec 14, 2021 7:54 am, edited 1 time in total.
I'm also Terminalforlife on GitHub.
1000
Level 6
Level 6
Posts: 1039
Joined: Wed Jul 29, 2020 2:14 am

Re: Good advices for bash developer.

Post by 1000 »

1. sh & bash
Long ago, at the beginning I often added at the beginning of the script #!/bin/sh
I copied it from the internet. For me, the script worked with it.

We usually use the bash shell. And I used bash, so I should use #!/bin/bash at the beginning of script.
If someone copies the script to another system with a different shell, the script may not work properly.

We should write scripts according to the shell we are building and running for.

We can check what we use

Code: Select all

$ ps -p $$
    PID TTY          TIME CMD
  44497 pts/4    00:00:00 bash
https://unix.stackexchange.com/question ... a-terminal

If you use from terminal $ sh file.sh or $ bash file.sh then, echo "$SHELL" from script not always it tell you the truth.


2. POSIX & echo & printf
Sometimes for example, command "printf" is more preferable because is compatible with POSIX.
POSIX allows you to write more portably. https://unix.stackexchange.com/question ... 190_565785

I want create text with new line something like

Code: Select all

$ echo -e "Line1\nLine2"
Line1
Line2
So I created code to test ( I know about " #!/bin/sh " but in this case it does not matter )

Code: Select all

#!/bin/sh

echo -e "Line1\nLine2"
echo "n "
echo -e 'Line3\nLine4'
echo "n "
echo -en "Line5\nLine6"
echo "n "
echo -en 'Line7\nLine8'
echo "n "
printf "Line9\nLine10"
echo "n "
printf 'Line11\nLine12'
echo "n "
printf "Line13\nLine14\n"
echo "n "
printf 'Line15\nLine16\n'
echo "n "
printf "%b\n" "Line17" "Line18"
echo "n "
printf "%b\n" 'Line19' 'Line20'
echo "n "
-------------------

A little test with bash

Code: Select all

$ bash file.sh
Line1
Line2
n 
Line3
Line4
n 
Line5
Line6n 
Line7
Line8n 
Line9
Line10n 
Line11
Line12n 
Line13
Line14
n 
Line15
Line16
n 
Line17
Line18
n 
Line19
Line20
n 
 
-------------------

A little test with sh

Code: Select all

$ sh file.sh
-e Line1
Line2
n 
-e Line3
Line4
n 
-en Line5
Line6
n 
-en Line7
Line8
n 
Line9
Line10n 
Line11
Line12n 
Line13
Line14
n 
Line15
Line16
n 
Line17
Line18
n 
Line19
Line20
n 


-------------------

You may find that

Code: Select all

printf "Line13\nLine14\n"
printf 'Line15\nLine16\n'
printf "%b\n" "Line17" "Line18"
printf "%b\n" 'Line19' 'Line20'
works identically in both cases.


3. #!/usr/bin/bash & #!/bin/bash & #!/usr/bin/env bash

The third way ( #!/usr/bin/env bash ) seems better and more portable.
This is true now. But it doesn't always have to be this way.

I'll try to explain problem.
Linux distributions, also BSD systems and maybe something else,
they like and want to choose where to install the software. ( above in the examples there are a different path )
Sometimes it is possible in the system to use a symbolic link to the appropriate shell.

Exist one more problem.
Bash and Linux distributions are usually a bit vulnerable to attacks because at one time the security level and priority were different.
For example with ~/.bashrc
https://wiki.bash-hackers.org/scripting/bashbehaviour
For example with $PATH or better, what the user can do by running malicious code (generally).
So, for now absolute path is more safe, than relative path in /usr/bin/env bash
But the same problem exist with any not builtin (in bash) command from the script.

I think when you want to write portably, you have no choice and for now you need use portability way.
Otherwise, you choose what you like.

So the method doesn't seem bad, but probably the system and bash need some tuning in future.
And maybe someone will come up with a better way in the future.
The env path itself may be redundant.

You can raise the level of security a little yourself.
For example you can check vulnerable files (bashrc, ...) what they can do (for example often can read aliases)
and secure them from root chattr +i /path/file
and open from root if you need chattr -i /path/file
How to secure $PATH variable, I don't know.


4. shellcheck
Highly recommended tool for beginners to analysing scripts. shellcheck file.sh
This tells you when or what and how to use.
I am using it to search for errors in the code. It doesn't always give you good advice, but I think it's a good tool.
Is available in the Synaptic package manager.
User avatar
Termy
Level 12
Level 12
Posts: 4248
Joined: Mon Sep 04, 2017 8:49 pm
Location: UK
Contact:

Re: Good advices for bash developer.

Post by Termy »

1.

You're referring to the shebang (Hash Bang). The #!/bin/sh is for the Bourne Shell (or derivatives, such as DASH), which isn't the same at all as using BASH, because the syntax and general capabilities of the shells are quite different.

Whether or not you need the shebang depends on how you or other users will be executing the script. For example, you can omit the the shebang if all you're going to do is execute it with, for example sh bourne_script.sh, but if the script (the file) has the execute bit set for the user, then you can, for example, run ./bourne_script.sh. You may also want to add the shebang for an editor's syntax highlighting, such as in Vi-IMproved, and/or to allow both forms of execution. When sourcing (. or source), the shebang isn't needed.
1000 wrote: If someone copies the script to another system with a different shell, the script may not work properly.
The user on that other system would need access to that shell in that location, or a shell abiding by the same applicable rules, such as DASH's intention to comply with Bourne Shell syntax.

2.
1000 wrote: Sometimes for example, command "printf" is more preferable because is compatible with POSIX.
I always use the printf builtin -- note that there is a GNU alternative, often installed by default at '/usr/bin/printf'. Not only is printf vastly superior to echo, but it's more consistent, reliable, and similar to printf in other languages, such as C, AWK, Perl, Python, etc.

That said, echo can be really useful as a one-off, for debugging, due to it taking all (except accepted flags, of course) arguments as something to echo. This is useful for knowing to what variables expand, for example.

You demonstrate the use of the %b format specification in printf, but it would've been better to use %s, for a string, where %b is for a string from which you wish to expand escape sequences, such as \n and \e[1;31m.

3.

As you explain, a common practice, one in which I often partake, is to use a shebang like:

Code: Select all

#!/usr/bin/env bash
It doesn't allow you to supply flags to BASH, but it's a more portable solution, without relying on BASH being in a specific location. For example, in Linux, BASH is typically '/bin/bash', but in Mac, it's somewhere like '/usr/local/bin/bash'.

Regarding the security issue of PATH: honestly, if you're so compromised, then you have many more far more serious problems. It's down to the user to ensure they manage their system properly, IMO, not the developers.

4.

Ah, ShellCheck. I'm of the exact opposite opinion, here. I strongly believe ShellCheck typically encourages people not to understand the shell and its limitations properly.

I cannot tell you how many times I've come across projects on, for example, GitHub, who's devs disregard PRs (pull requests) simply because the code clashes with ShellCheck, yet it's perfectly valid code and there are no actual issues. The other devs just don't care, because they don't understand what's going on. They'll argue tooth-and-nail that what I'm doing is wrong, when it's not and it's all well-documented in places like manual pages, it's just that ShellCheck specifically is telling them it's 'wrong'. :lol:

Blindly following ShellCheck is something I will never ever recommend. The only time I think something like ShellCheck might be useful, is for very large projects, for experienced shell programmers to get an overview of any potential problems they may have overlooked, especially when multiple people contribute to the code. That said, I'd argue that's why we have tester scripts and debugging code in our larger and/or more complex projects.

I never used anything like ShellCheck in any language. I rely on the language itself to tell me when something is wrong. I taught myself everything I know about programming, and I honestly believe I was better off for it. I found out myself, through experience, why, for example, using [ CONDITION -a CONDITION ] can be problematic; instead of relying on something to nag me about it, I experienced it first hand and did my own research on the matter. Yet, ShellCheck will light up like a Christmas tree, encouraging their way of doing things. Although ShellCheck will give an explanation, I imagine that people typically won't properly understand or even read it, hence everything I've said above.

Projects I come across from people enforcing ShellCheck in their projects typically have wildly inefficient code and demonstrate a limited understanding of the shell in which they're programming.

YMMV, but this has been my incredibly frustrating experience over the years. Knowing exactly what you're doing from years of experience and research, only to have someone with a naive understanding of the code come along with ShellCheck, telling me the code I spent time contributing to a project is all wrong because ShellCheck says so, is unbelievably aggravating, and why I don't tend to put my time in such projects.

That turned into a bit of a rant -- sorry. :lol:
Last edited by Termy on Tue Dec 14, 2021 8:12 am, edited 2 times in total.
I'm also Terminalforlife on GitHub.
1000
Level 6
Level 6
Posts: 1039
Joined: Wed Jul 29, 2020 2:14 am

Re: Good advices for bash developer.

Post by 1000 »

This is example with for loop and seq.

Code: Select all

for M in $( seq 6 8 ) ; do echo "M= $M" ; done 
- I build script which can just execute a function.
The script seems to be working correctly.

Code: Select all

M= 6
M= 7
M= 8
- The same script can execute a function in an infinite loop from a different file.
The script does not work correctly.

Code: Select all

M= 6
7
8
I don't know what the cause is.
But my guess is that under certain conditions the same function might be interpreted differently.

See an example without quotation marks and below an example with quotation marks.

Code: Select all

$ for M in $( seq 6 8 ) ; do echo "M= $M" ; done
M= 6
M= 7
M= 8

$ for M in "$( seq 6 8 )" ; do echo "M= $M" ; done
M= 6
7
8

Solution
If you have problem with " for loop ", instead of this, you can use while loop.

Code: Select all

M=6
while [ $M -le 8 ] ; do
	echo "M=$M"
	M=$[$M+1]
done
------------------------

I seen
Latest bash version 3.0+ has inbuilt support for setting up ranges:
...

Code: Select all

#!/bin/bash
for i in {1..5}
do
   echo "Welcome $i times"
done
https://www.cyberciti.biz/faq/bash-for-loop/

But for now, I can't correct implement it in my script.

Code: Select all

for M in {$[$COUNT_LOCAL + 1]..${#TAB3[@]}} ; do echo "M= $M" ; done

M= {6..8}
User avatar
Termy
Level 12
Level 12
Posts: 4248
Joined: Mon Sep 04, 2017 8:49 pm
Location: UK
Contact:

Re: Good advices for bash developer.

Post by Termy »

1000 wrote: Mon Nov 08, 2021 5:19 pm ...
A `while` loop is the better tool for the job when iterating over a range using variables and brace expansion, when compared to a standard `for` loop. There is a way to do it with a standard `for` loop, but it involves `eval` and is simply the wrong tool for the job:

Code: Select all

Start=1
End=5
for Num in `eval printf '%d\\\n' {$Start..$End}`; {
    printf '%d\n' $Num
}
Or if you prefer `echo`, you can use `eval echo {$Start..$End}` instead.

Ultimately though, you're probably better off using a C-style `for` loop:

Code: Select all

Start=1
End=5
for ((Iter=Start; Iter <= End; Iter++)); {
    printf '%d\n' $Iter
}
I'm also Terminalforlife on GitHub.
1000
Level 6
Level 6
Posts: 1039
Joined: Wed Jul 29, 2020 2:14 am

Re: Good advices for bash developer.

Post by 1000 »

Other example with relative path and absolute path.
Maybe it is obvious to everyone, but I preferred to write.

For me this is problematic.
For example when I had in script

Code: Select all

source lib/lib.colors.sh
Directory structure:

Code: Select all

$ tree
.
├── lib
│   └── lib.colors.sh
└── n.sh
and when I run script from the folder where it is located - script working
but when I will try run script from cron - script not will work

Because lib/lib.colors is relative path for bash.
And cron for root jobs is running from /root/
so in /root/lib/lib.colors file not exist.

So if a user wants to write more portable code then he should use absolute paths.
Easier said, sometimes harder to do.

Example source code

Code: Select all

#!/bin/bash


SCRIPT_NAME="$(basename $0)"
echo "NAME of script = $SCRIPT_NAME"
echo " "

##      We will create the paths to the directories
PATH_NAME_1="$(pwd)"
    echo "pwd PATH_NAME_1 = $PATH_NAME_1/${SCRIPT_NAME}"
    [[ -f "${PATH_NAME_1}/${SCRIPT_NAME}" ]] && echo "    Script exist in this dir :)" || echo "     Script in this dir not exist!"
    echo " "
    
PATH_NAME_2="$(dirname "$(which $0)")"
    echo "which PATH_NAME_2 = $PATH_NAME_2/${SCRIPT_NAME}"
    [[ -f "${PATH_NAME_2}/${SCRIPT_NAME}" ]] && echo "    Script exist in this dir :)" || echo "     Script in this dir not exist!"
    echo " "
      
PATH_NAME_3="$(dirname "$(realpath $0)")"
    echo "realpath PATH_NAME_3 = $PATH_NAME_3/${SCRIPT_NAME}"
    [[ -f "${PATH_NAME_3}/${SCRIPT_NAME}" ]] && echo "    Script exist in this dir :)" || echo "     Script in this dir not exist!"
    echo " "
    
PATH_NAME_4="$(dirname "$0")"
    echo "bash parameter \$0 PATH_NAME_4 = $(dirname "$0")/${SCRIPT_NAME}"
    [[ -f "${PATH_NAME_4}/${SCRIPT_NAME}" ]] && echo "    Script exist in this dir :)" || echo "     Script in this dir not exist!"


echo " "
echo " "
- When the script is run from the directory where the script resides - everything works
- When the script is run from another directory ( for example from /var/log/ ) - not everything works
$ bash /home/user/Desktop/test/n.sh
NAME of script = n.sh

pwd PATH_NAME_1 = /var/log/n.sh
Script in this dir not exist!

which PATH_NAME_2 = ./n.sh
Script in this dir not exist!

realpath PATH_NAME_3 = /home/user/Desktop/test/n.sh
Script exist in this dir :)

bash parameter $0 PATH_NAME_4 = /home/user/Desktop/test/n.sh
Script exist in this dir :)
You can also try move script to other place and check how working.

Conclusions
- "pwd" path is variable
- "which" path will empty if script is not in any of the directories declared by the "$PATH" environment variable.
- "realpath" works perfectly
- "dirname $0" worked but I am not convinced if is it a perfect idea.

If you run the code where the script is, the output fragment will look like this
bash parameter $0 PATH_NAME_4 = ./n.sh
Script exist in this dir :)
The script works here and there, but here ( from the place of the script ) not have full path, ( ./n.sh )
It may not matter, but I prefer to be careful.

I can try also test

Code: Select all

PATH_NAME_5="$(readlink -f "$(dirname "$0")")"
or

Code: Select all

PATH_NAME_6="$(dirname "$(readlink -f "$0")")"
However with " realpath " method is the most recommended method.
hpmini
Level 1
Level 1
Posts: 2
Joined: Tue Aug 16, 2022 5:36 am

Re: Good advices for bash developer.

Post by hpmini »

I remember a lot of head scratching a long time ago, I forgot exactly what the context was. But the gist is that, the use of

Code: Select all

echo
in a shell script gave me some weird issues. In the end, I found out the root cause was because the echo command is a shell built-in. And then, there is another available

Code: Select all

/bin/echo
command and I got these two mixed up. It was a holy cow moment for me.
User avatar
Termy
Level 12
Level 12
Posts: 4248
Joined: Mon Sep 04, 2017 8:49 pm
Location: UK
Contact:

Re: Good advices for bash developer.

Post by Termy »

hpmini wrote: Tue Aug 16, 2022 10:38 pm [...]
That's a good one. Many people, myself included, prefer to use the printf builtin than echo, because it's more reliable and far more flexible (especially in BASH!), but it's not necessary if you have super-basic needs. Regarding external alternatives to builtins, there are many more than just '/bin/echo'. Here is a list of common ones which come to mind:

'/bin/echo' / echo
'/bin/false' / false
'/bin/kill' / kill
'/bin/printf' / printf
'/bin/pwd' / pwd
'/bin/true' / true
'/usr/bin/getopt' / getopts
'/usr/bin/test' / test

Mostly, these alternatives exist for compatibility reasons. The alternatives sometimes have slight differences to the BASH builtins of today, like '/usr/bin/getopt' (AFAIK). The '/bin/true' one is useful for system user login shell assignment, which you'll see a lot if you look in '/etc/passwd' for UIDs < 1000 and != 65534.
I'm also Terminalforlife on GitHub.
1000
Level 6
Level 6
Posts: 1039
Joined: Wed Jul 29, 2020 2:14 am

Re: Good advices for bash developer.

Post by 1000 »

Small trifle - source files / libraries for bash scripts.

I noticed that someone uses security ( exit from script ), if the source file does not exist.

I was surprised, because one of my scripts is specific.
When source not exist is error, the script keeps running,
the settings are reset and the error is not visible.
It's a rare case, but possible.


Some users use " set -e " at the beginning of the script.

Code: Select all

set --help
...
-e  Exit immediately if a command exits with a non-zero status.
...
But sometimes we can't use it, especially when we expect errors from other commands.
We can use additional function

Code: Select all

#!/bin/bash

START_IF_EXIST() 
{
    if [[ -f "$1" ]] ; then
        source "$1"
    else
        echo "Error: Source not found: $1" 1>&2
        exit 1
    fi
}

START_IF_EXIST "$PATH_OF_SCRIPT"/data/functions.of.script.bash

Or little different way for most errors example

Code: Select all

#!/bin/bash

EXIT_IF_ERROR() 
{
    if [[ ! "$?" -eq 0 ]] ; then
        echo "$2" 1>&2
        exit 1
    fi
}

EXIT_IF_ERROR "$( source "$PATH_OF_SCRIPT"/data/functions.of.script.bash )" "Your additional message:"
Of course, we can change it according to our needs,
change color, add exit status to message "$?", use "printf" command instead of "echo" ,
make exit status conditional
for example

Code: Select all

(( $1 > 0 )) && exit $1
in https://github.com/terminalforlife/Extr ... le-bash.sh
1000
Level 6
Level 6
Posts: 1039
Joined: Wed Jul 29, 2020 2:14 am

Re: Good advices for bash developer.

Post by 1000 »

Problem with " Shell Parameter Expansion " + echo command
https://www.gnu.org/software/bash/manua ... nsion.html

For example
I tried build a more complicated example
show from left 4 characters ( "a -n" ), next from right 2 characters ( "-n" )

Code: Select all

$ x='a -nc d e' ; echo "$( y="${x::4}" ; echo "${y: -2}" )"
and output is empty.

Because the double quotation mark ( " " ) makes that the content is interpreted in output.
At the same time, we cannot use single quotation marks ( ' ' ),
because we want to interpret the action for example ${x::4}.

Solution.

Code: Select all

$ x='a -nc d e' ; printf '%s\n' "$( y="${x::4}" ; printf "%s" "${y: -2}" )"
-n
Here with printf we declare that we want have string ( %s ) in the output and problem now not exist.
Locked

Return to “Scripts & Bash”