When writing the GAR script, I need to determine the path of the GAR script to its own directory on the file system while it is running. Based on this path, the gar.css file can be deployed to the root of the document project, because gar.css is in the same directory as the gar.css script, which needs to find it based on its own location, otherwise the user of the gar.css script will have to provide the path to gar.css, which is inconvenient.

BASH_SOURCE

Some say you can get the script’s own path with the following statement

my_path=$(cd $(dirname "BASH_SOURCE[0]") && pwd)

I thought for a moment. Really thought about… I even looked at the description of BASH_SOURCE in the Bash reference manual:

There is FUNCNAME in this description, and I have found the following description in the manual:

Who knows what they’re saying? For now, all I know is that BASH_SOURCE is an array variable that stores a set of Source filenames. ${BASH_SOURCE[0]} : ${BASH_SOURCE[0]} :

#! /bin/bash echo ${BASH_SOURCE[0]}

I’ll call the above script foo, put it in a directory I defined specifically for the script, give it executable permissions, and execute foo,

$ foo
/home/garfileo/.my-scripts/foo

Instead of implementing foo,

$ bash /home/garfileo/.my-scripts/foo
/home/garfileo/.my-scripts/foo

Another way to do it,

$ bash $(foo)
/home/garfileo/.my-scripts/foo

Another way to do it,

$ source $(foo)
/home/garfileo/.my-scripts/foo

In Bash’s syntax, $(…) Called command substitution, it opens up a subshell that executes the commands in parentheses and returns the results as text.

What the above experiment reveals is that the Foo script can determine where it is when it is run. Foo can do it, Gar can do it.

BASH_SOURCE[0]$0What’s the difference?

It seems that the first argument on the command line, $0, can tell a script where it is. To replace foo

#! /bin/bash echo $0

Do the experiment again:

$ foo
/home/garfileo/.my-scripts/foo
$ bash $(foo)
/home/garfileo/.my-scripts/foo
$ source $(foo)
bash

In the above experiment, it was only when the source was used to execute foo that the result was not the location of the foo script, but the bash.

What’s so special about the source command?

sourceThe command

The source command loads the specified Shell script (which can be Bash or another Shell) as part of the currently running Shell. So, the experiment we did in the last video

$ source $(foo)

The output is bash, because the contents of the foo script are loaded by Source into the current bash and become part of the latter, so $0 is not foo, but bash.

The Source command is the Dafa or Northern Nether Magic of the Bash world.

Because of the source command, ${BASH_SOURCE[0]} is more robust to get the script’s own path.

The function of stack

${BASH_SOURCE[0]} = ${BASH_SOURCE[0]} = ${BASH_SOURCE[0]}

function
${FUNCNAME[$i]}In the file
${BASH_SOURCE[$i]}In the definition, in
${BASH_SOURCE[$i+1]}Is called in.

What does that mean?

The function ${FUNCNAME[0]} is defined in the file ${BASH_SOURCE[0]} and is called in the file ${BASH_SOURCE[1]}. What does that mean?

I need to modify foo by defining a simple function in it:

#! /bin/bash function test { echo ${BASH_SOURCE[*]} echo ${FUNCNAME[*]} }

${X[*]} merges everything in the array X into a piece of text (or a string).

Then create the script bar, source foo, and call the function text:

#! /bin/bash source foo test

Then perform

$ bash ./bar
/home/garfileo/.my-scripts/foo ./bar
test main

Based on this output, it’s easy to deduce

  • ${BASH_SOURCE[0]/home/garfileo/.my-scripts/foo;
  • ${BASH_SOURCE[1]bar;
  • ${FUNCNAME[0]}test;
  • ${FUNCNAME[1]}main.

Thus, it can be asserted that the function test is defined in the file foo and called in the file bar.

To make things clearer, modify the BAR script:

#! /bin/bash source foo function bar { test } bar

Execute bar again:

$ bash ./bar
/home/garfileo/.my-scripts/foo ./bar ./bar
test bar main

As you can infer from this, main is the function at the bottom of the stack and is the caller to all of Bash’s functions.

An array of

Both BASH_SOURCE and FUNCNAME are arrays. They’re the same as any random array that I define myself

blab=(a b c d e f)

There is no difference in nature. ${blab[0]}, ${blab[1]}, ${blab[*]}…… Although it may seem strange, this is the syntax for retrieving elements from arrays.

${blab[0]} gets the first element from blab. ${blab[1]} fetches the second element from blab. And so on. Although Bash is not very good at math, more like a liberal arts student, it does support arithmetic in terms of array subscripts, such as ${blab[1+2]} to get the fourth element from the blab.

As mentioned above, ${blab[*]} takes all the elements from blab and organizes them into a piece of text. The syntax to get all the elements of an array, as well as ${blab[@]}, but it gets multiple segments of text separated by IFS defined symbols. IFS is an environment variable for Bash, and its value is space by default. Thus, multiparagraph text is text with Spaces as the spacing symbol.

Multiple paragraphs of text are common in Bash loops. For example,

for i in a b c d e
do
    echo $i
done

This code can be printed in a terminal window (a command line window)

a
b
c
d
e

Since ${blab[@]} takes all the elements in blab and presents them as piecewise text, the above loop is equivalent to the following code

for i in ${blab[@]}
do
    echo $i
done

However, if the array looks like this

blab=(a b c d "e f g" h)

${blab[@]} does not consider “e, f, g” to be one element in the array, but three. To limit its indulgence, use double quotation marks. For example,

for i in "${blab[@]}"
do
    echo $i
done

This code will produce output:

a
b
c
d
e f g
h

There seems to be no rhyme to the behavior of putting double quotes around access statements for array elements. This form is similar to the past tense of some words in English.

An argument to a command or function

The arguments to a command or function are also arrays, but the syntax for accessing elements in them is much simpler. For example, $0, $1, $2… , can access the first, second, third, third… A parameter. You use $@ to access all the parameters, but you get segmented text. You can also access all the parameters with $*, but you get a piece of text.

What I find useful is the slicing syntax for arrays. For example,

${blab[@]:2}

Gets the third element in blab and all elements after that, and the results are given in piecewise text.

${blab[@]:2:4}

Gets the third element in blab and the next three elements, a total of four elements.

Similarly, a similar syntax is used for arguments to commands or functions:

${2} @ : ${@ : 3:4}

where

It seems to have strayed a little too far from the point of view of the question. Now, back to the beginning:

my_path=$(cd $(dirname ${BASH_SOURCE[0]}) && pwd)

I was wondering if this line of code was a little verbose

my_path=$(dirname ${BASH_SOURCE[0]})

Not line?

For example, based on the previous experiment, ${BASH_SOURCE[0]} can give the absolute path to the location of the foo script:

/home/garfileo/.my-scripts/foo

If you execute dirname on this path, you get the location of the foo script, i.e

$ dirname /home/garfileo/.my-scripts/foo
/home/garfileo/.my-scripts

Why CD to $(dirname ${BASH_SOURCE[0]} and then PWD?

It was a coincidence that the idea worked. Because I put the foo script in the PATH set by the system. When Bash executes foo, it gets its absolute path and saves it to ${BASH_SOURCE[0]}. If foo is executed using a relative path, this idea breaks down. $(dirname ${BASH_SOURCE[0]}, then PWD outputs the current working directory. Because Bash’s command substitution opens a subshell of the current Shell, switching working directories in a subshell does not affect the current Shell.

In fact, a more robust way to get the script’s own path would be:

my_path="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

Remember, whitespace is the enemy of Bash commands and functions. When you suspect that a Bash command or function argument might contain Spaces, be careful to enclose them in double quotation marks, even if they are redundant.