Why is ./ sometimes needed?

I typed Example 2-3 from Cooper`s Advanced Bash-Scripting Guide into my ~/bin , and it will only run if I include ./ before the filename. My other scripts in the same directory do not behave this way. $PATH includes ~/bin . I copied the file into /usr/local/bin , and it runs without ./ .

./ is needed to run a script not contained in the PATH variable and located in the current directory.

look into the PATH-Variable and check if the directory of the script is really contained in it. Be aware to check for typos! This applys to all scripts in the same directory.

1 Like

Some more info would be beneficial. For example:

  • Is the script really located in ~/bin ?
  • Any error messages?
  • Are the permissions set correctly?
  • Does the script refuse to start, or does it fail somewhere in the middle? The -x (xtrace) option might help.
  • Does it use a special interpreter / shell, not your default one?
  • Do you have the "shebang" set?
  • How does it differ from the scripts that do run?

Just to name a few...

1 Like

Expanding a little bit on what stomp and RudiC have already said...

What modes are displayed for the files in ~/bin that run without ./ when you run the command:

ls -l ~/bin

What modes are displayed for the files in ~/bin that do not run without ./ ?

What other directories are in $PATH before ~/bin ?

Do any of those directories contain a file with the same name as one of the files you are trying to execute?

Normally a shell will execute the first executable file found while going through the directories listed in $PATH in the order in which they appear in $PATH . Some shells will remember where they found an executable file on a previous search and won't look again unless a pathname that had been remembered stops working.

If an execute permission bit that allows you permission to execute it isn't set on a file that you're trying to execute, you usually won't be permitted to execute it by trying to run filename or by ./filename , but the command pathname_of_shell filename will work if filename is a valid script in the shell language recognized by the shell you're invoking with pathname_of_shell .

1 Like

@stomp--I double-checked. The file in question is Cleanup2.

echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/m*****/bin
   
~/bin$ ls
case4     counter    foo           lw2                     posit-params3
cleanup2  file_info  longest-word  old_sys_info_page       sys_info_page
cleanup3  file-info  lw            old_sys_info_page.html  sys_info_page.html

--- Post updated at 04:15 PM ---

@RudiC and @Don

the file is located in ~/bin (see above)

error message

~/bin$ sudo cleanup2
sudo: cleanup2: command not found

permissions

~/bin$ ls -l cleanup2
 -rwxr-xr-x 1 m***** m***** 459 Jan 28 15:07 cleanup2


~/bin$ ls -l cleanup3
-rwxr-xr-x 1 m***** m***** 2057 Jan 29 06:13 cleanup3

shebang

#!/bin/bash
   # Proper header for a Bash script

As far as differences, go, I can't find any. Cleanup3 runs fine, but Cleanup2 won't.
Don, I tried locate cleanup and found some other files containing that string, but they aren't in $PATH

Note that cleanup2 and cleanup3 are not the same as Cleanup2 and Cleanup3 ; case matters in the shell command language!

What output do you get from the commands:

od -bc ~/bin/cleanup2 | head -n 10
od -bc ~/bin/cleanup3 | head -n 10
1 Like

You didn't mention you're using sudo . The effective user (by default) is root , then. root 's $PATH does NOT have ~/bin appended, or, if it has, it won't point to YOUR $HOME nad thus doesn't find cleanup2 .

Does sudo cleanup3 run correctly?

Please make sure to post such important info in the first place in future!

1 Like

To add to already quoted:

<CR> = The Enter/Return key "  |
                             <-' "
 ./executable[.extension]<CR>
 ^^         ^
 ||         |
 ||         +---> The filename to run and MUST be set to executable, using 'chmod' if necessary.
 |+-------------> The current directory/drawer/folder path seperator.
 +--------------> The __pointer__ to the current directory/drawer/folder.

OR...

 /absolute/path/to/your/executable[.extension]<CR>

OR as in your case:

 executable[.extension]<CR>

From any directory/drawer/folder that is inside the $PATH environment variable.

EDIT:
Even these will work from your current path, but not recommended:

././executable[.extension]<CR>
AND
./././executable[.extension]<CR>
1 Like

I can only emphasize on what RudiC already said: running sudo command is a completely different thing than running command . Basically sudo does:

1) switch to another user acount (by default this is root)
2) execute command as this user
3) switch back.

Of course, a different user can have (and most probably has) a different environment and when you change yours then his is not affected at all. It does not matter what is in your PATH, what matters is what is in roots PATH.

Try sudo echo $PATH and most probably /your/home/bin will not be included in it. If you can change it you could do that although i think it would be a bad idea.

Instead, put a script that should be run by several users (and eventually as root) in /usr/local/bin or in either /usr/local/sbin or /root/bin if it should only be run as root. Put /root/bin or /usr/local/sbin in roots PATH in this case (i suppose /usr/local/bin to be already included there). Then you can use the command you used.

I hope this helps.

bakunin

PS: you probably wonder why it works with ./command . The reason is that the "." for "current directory" is expanded by the shell even before any attempt of executing any command is made. Therefore the shell replaces "." with something like "/path/to/your/home/bin" and only then executes sudo /path/to/your/home/bin/cleanup2 so that the user you switch to doesn't have to have it in its PATH to find it.

bakunin

3 Likes

To all: thanks for your input!

Today I learned: $PATH is not the same for myself and root.

@Don--I typed Cleanup in the post but I wasn't doing so in the script.
Here is the output of those commands:

  ~$ od -bc ~/bin/cleanup2 | head -n 10
 0000000 043 041 057 142 151 156 057 142 141 163 150 012 012 012 043 040
           #   !   /   b   i   n   /   b   a   s   h  \n  \n  \n   #    
 0000020 120 162 157 160 145 162 040 150 145 141 144 145 162 040 146 157
           P   r   o   p   e   r       h   e   a   d   e   r       f   o
 0000040 162 040 141 040 102 141 163 150 040 163 143 162 151 160 164 012
           r       a       B   a   s   h       s   c   r   i   p   t  \n
 0000060 043 040 143 154 145 141 156 165 160 062 072 040 151 155 160 162
           #       c   l   e   a   n   u   p   2   :       i   m   p   r
 0000100 157 166 145 144 012 043 040 122 165 156 040 141 163 040 162 157
           o   v   e   d  \n   #       R   u   n       a   s       r   o
------------------------------------------------------------------------
           ~$ od -bc ~/bin/cleanup3 | head -n 10
  0000000 043 041 057 142 151 156 057 142 141 163 150 012 012 012 043 040
           #   !   /   b   i   n   /   b   a   s   h  \n  \n  \n   #    
 0000020 143 154 145 141 156 165 160 063 012 043 040 040 127 141 162 156
           c   l   e   a   n   u   p   3  \n   #           W   a   r   n
 0000040 151 156 147 072 012 043 040 040 055 055 055 055 055 055 055 012
           i   n   g   :  \n   #           -   -   -   -   -   -   -  \n
 0000060 043 040 040 124 150 151 163 040 163 143 162 151 160 164 040 165
           #           T   h   i   s       s   c   r   i   p   t       u
 0000100 163 145 163 040 161 165 151 164 145 040 141 040 156 165 155 142
           s   e   s       q   u   i   t   e       a       n   u   m   b
 

--- Post updated at 07:52 PM ---

@ RudiC and @bakunin

Thanks to your guidance, I strongly suspect the problem is not having my home directory is root's $PATH .

Cleanup3 was only running because I had placed a copy of it in /usr/local/bin .

Now I just need to figure out how to write bakunin's script!

You presented the solution already. Why don't you put cleanup2 into /usr/local/bin as well?

1 Like

Good point, EXCEPT, if you look at the hex-dump of cleanup2 it quotes, "Run as ro[ot]"...

 0000100 157 166 145 144 012 043 040 122 165 156 040 141 163 040 162 157
           o   v   e   d  \n   #       R   u   n       a   s       r   o

So there must be a reason why it needs to be run as root.
Just an observation.

1 Like

Chances are that root does run scripts in /usr/local/bin as proven by

2 Likes

Hi RudiC...

Yes but Cleanup3 does not run as root and could be anywhere.
However maybe Cleanup2 has to be in more secure location so that any 'Tom, Dick or Harry' can't execute it unless they have root access.
Maybe it is 'critical', term used loosely, to the correct 'cleanup' method. It would be interesting to know the access rights of Cleanup2.
This was my point, perhaps I am being too pedantic here...

(Anyhow, I am rigged for silent running on this thread now.)

Bazza.

1 Like

Well, wisecaracker, I see your point, but - IF a script does such delicate things that needs root privileges, it certainly should NOT reside in a user's $HOME/bin . And, sudo is so flexible to setup that only the eligible user can use cleanup2 in a public directory.

1 Like

Except that your shell will evaluate $PATH before executing sudo . Instead you would need to do the somewhat iffy looking

sudo bash -c "eval echo \$PATH"

or better still get use sudo -i and check your path.

Agreed. So place the program in /usr/local/sbin , which on my system at least, is in root's path when using sudo . And make it executable (and readable?) only by root.

Andrew

2 Likes

I appreciate the help; I've learned a lot!

Here`s the original script, which I did not write.

#!/bin/bash
 

# Proper header for a Bash script
# cleanup2: improved
# Run as root, of course.
# Insert code here to print error message and exit if not root.

LOG_DIR=/var/log
# Variables are better that hard-coded values.
cd $LOG_DIR

cat /dev/null > messages
cat /dev/null > wtmp

echo "Logs cleaned up."

exit    #  The right and proper method of "exiting" from a script.
        # A bare "exit" (no parameter) returns the exit status
        #+ of the preceding command. 

IS THAT IT, (and needs to be run from root)?
Why use:

cat /dev/null > messages
cat /dev/null > wtmp

When:

: > messages
: > wtmp

......is much simpler.
Useless use of cat?
No disrespect to you Xubuntu56, you are innocent of this one...
BUT THAT........
........guys was the improved version, what was the original like I wonder!

1 Like

Hopefully this does NOT attach to MY previous post.
A rewrite of the code to have an optional user path.
Usage: cleanup2 [/optional/full/path/to]

#!/bin/sh
# Usage: cleanup2 [/optional/full/path/to]
# POSIX compliant.
# Due to '/var/log/' this HAS to be run from root.
# No need to detect root as the error generated is enough.

# Attempt to obtain the user path argument.
LOG_DIR="$1"

# Fall back to the default if user path is NOT supplied.
if [ "${LOG_DIR}" = "" ]
then
    LOG_DIR="/var/log"
fi

# *****************************
# Not required but commented out so you can try both meathods.
# cd "${LOG_DIR}"
# Clear require files. ":" is a NULL, NOP or true command.
# : > messages
# : > wtmp
# These eight lines can be deleted.
# *****************************

# Create empty files in the required path.
: > "${LOG_DIR}"/messages
: > "${LOG_DIR}"/wtmp

# Print success and exit script.
echo "Success! Logs cleaned up."
exit

Results:
OSX 10.14.1, default bash terminal, code calling 'sh'.

AMIGA:amiga~/Desktop/Code/Shell> chmod 700 cleanup2
AMIGA:amiga~/Desktop/Code/Shell> ls -l /tmp/
total 0
drwx------  4 root   wheel  128 16 Nov 12:34 PKInstallSandbox.MMTz8b
drwx------  3 amiga  wheel   96 30 Jan 18:07 com.apple.launchd.CPHRzeQ4Vr
drwx------  3 amiga  wheel   96 30 Jan 18:07 com.apple.launchd.qgu8OpjEvP
drwxr-xr-x  2 root   wheel   64 30 Jan 18:00 powerlog
AMIGA:amiga~/Desktop/Code/Shell> ./cleanup2 /tmp
Success! Logs cleaned up.
AMIGA:amiga~/Desktop/Code/Shell> echo $?
0
AMIGA:amiga~/Desktop/Code/Shell> ls -l /tmp/
total 0
drwx------  4 root   wheel  128 16 Nov 12:34 PKInstallSandbox.MMTz8b
drwx------  3 amiga  wheel   96 30 Jan 18:07 com.apple.launchd.CPHRzeQ4Vr
drwx------  3 amiga  wheel   96 30 Jan 18:07 com.apple.launchd.qgu8OpjEvP
-rw-r--r--  1 amiga  wheel    0 30 Jan 19:44 messages
drwxr-xr-x  2 root   wheel   64 30 Jan 18:00 powerlog
-rw-r--r--  1 amiga  wheel    0 30 Jan 19:44 wtmp
AMIGA:amiga~/Desktop/Code/Shell> ./cleanup2
./cleanup2: line 25: /var/log/messages: Permission denied
AMIGA:amiga~/Desktop/Code/Shell> echo $?
1
AMIGA:amiga~/Desktop/Code/Shell> _
1 Like

Andrew, you are right. My excuse is that my dog ate the single quotes. ;-)) It was meant to be sudo echo '$PATH' , but you are equally right about sudo -i .

This observation is correct but the return code being returned will be that of echo , no? To be honest i have never seen this command reurn anything else than TRUE. I would have a hard time even conceiving a situation where it fails. But you probably want to base the return code not on the success ot the echo but on the success of cleaning the logs.

It makes sense to base the return code of scripts on the success (or lack of it) of certain critical commands. The way to do that is like this: suppose your script will have - at its core - to execute three commands: command1 , command2 and command3 in that order. Now, each of these commands could fail and you want to be able to find out how your script has failed - has it failed to execute command1? Or command2 or command3? So let us say you want to exit your script with:

0 when it succeeds (that is: all three commands excuted successfully);
1 when excuting command1 failed (the others are skipped in that case);
2 when excuting command2 failed (the last is skipped in that case);
3 when excuting command3 failed;

The logic to implement that is like this:

#! /bin/bash
if ! command1 ; then
     exit 1
fi
if ! command2 ; then
     exit 2
fi
if ! command3 ; then
     exit 3
fi
exit 0

The if executes any command and branches based on the return code of that command "!" is just a logical NOT and means branch if the command returns a non-zero (=logical FALSE) return code. Note that it doesn't matter exactly how the commands fail - they could have all sorts of error codes. This is why this is only the most basic form of logging, but since you do understand now the principle i am sure you can come up with more elaborate solutions should you need them.

Dear brother of the church of ongoing simplification, you surely noticed by now that you wasted four (!!) perfectly usable keystrokes with your solution too, no? This:

> messages
> wtmp

is even simpler than the much simpler solution. ;-))

I hope this helps.

bakunin

2 Likes