10

I've created a simple C program like so:

int main(int argc, char *argv[]) {

    if (argc != 5) {
       fputs("Not enough arguments!\n", stderr);
       exit(EXIT_FAILURE);
    }

And I have my PATH modified in etc/bash.bashrc like so:

PATH=.:$PATH

I've saved this program as set.c and am compiling it with

gcc -o set set.c

in the folder

~/Programming/so

However, when I call

set 2 3

nothing happens. There is no text that appears.

Calling

./set 2 3

gives the expected result

I've never had a problem with PATH before and

which set

returns ./set. So it seems the PATH is the correct one. What's is happening?

  • 10
    It is relatively dangerous to add '.' to your PATH. Better to just use ./ when executing something from the local directory, or move the executable into a well known directory like ~/bin/ – TREE Nov 24 '15 at 20:54
  • 7
    It is also a bad idea to call your test program test for basically the same reason; test is a shell built-in too. – Jonathan Leffler Nov 25 '15 at 08:45
  • @JonathanLeffler And yet for quick tests calling a program test seems to make sense. Of course by the time you put it in your PATH you really should have come up with a different name. And until you put the program in your PATH you'd have to invoke it as ./test anyway. So it is kind of OK to use the name test for a program as long as it is a quick test which you intend to delete before the end of the day. – kasperd Nov 25 '15 at 14:08
  • 1
    @kasperd: As far as I'm aware, the conventional name for a quick test program is foo. – hmakholm left over Monica Nov 25 '15 at 18:06
  • If you name it ls then whenever you go to see if it exists it will run (but only if you modify your path as you did in the question). – ctrl-alt-delor Dec 22 '15 at 23:41

4 Answers4

25

Instead of using which, which doesn't work when you need it most, use type to determine what will run when you type a command:

$ which set
./set
$ type set
set is a shell builtin

The shell always looks for builtins before searching the $PATH, so setting $PATH doesn't help here.

It would be best to rename your executable to something else, but if your assignment requires the program to be named set, you can use a shell function:

$ function set { ./set; }
$ type set
set is a function
set ()
{
    ./set
}

(That works in bash, but other shells like ksh may not allow it. See mikeserv's answer for a more portable solution.)

Now typing set will run the function named "set", which executes ./set. GNU bash looks for functions before looking for builtins, and it looks for builtins before searching the $PATH. The section named "COMMAND EXECUTION" in the bash man page gives more information on this.

See also the documentation on builtin and command: help builtin and help command.

yellowantphil
  • 927
  • 2
  • 9
  • 20
  • 3
    You recommend type over which, but don't give any reason why. (I know why, but somebody who needs the recommendation wouldn't.) – cjm Nov 24 '15 at 20:51
  • 1
    @cjm Here's a whole treatise on why not which: https://unix.stackexchange.com/questions/85249/why-not-use-which-what-to-use-then – Anthony Geoghegan Nov 24 '15 at 21:43
  • Very informative. You'd never guess there's so much controversy over such a seemingly simple command that does a simple task – Ganea Dan Andrei Nov 24 '15 at 23:12
  • 4
    @GaneaDanAndrei Mainly, use type instead of which, don't name your program "set", and realize that function set { ./set; } is an ugly hack that you should probably avoid. – yellowantphil Nov 24 '15 at 23:15
11

set is a builtin in bash (and probably most other shells). This means that bash will not even search the path when looking for the function.

As a side remark, I would strongly advise against adding . to the path for security reasons. Imagine for example cding out of /tmp after any other user added an executable file /tmp/cd.

10

set isn't just a builtin, it is a POSIX special builtin. There are a few builtin commands which are standards-specified to be found in a command search before anything else - $PATH is not searched, function names are not searched, and etc. Most builtins which are not special are actually required by the POSIX standard to be found in your $PATH before the shell will run any of its own builtin procedures. This is true of echo and most others (though whether the standard is honored in this respect has been a matter of contention at the open group mailing lists in the past), but not of set, trap, break, return, continue, ., :, times, eval, exit, export, readonly, unset, or exec.

All of these are reserved names of the shell, and they have special attributes other than their order of preference for command search as well. For example, you cannot define a shell function with any of those names in a standards-compliant shell. This is a good thing - it enables people to write portable scripts safely. These are baseline commands from which an experienced script-writer can establish a secure and reliable foothold in his or her environment. Invading this namespace is not advisable.

However, if you do desire to invade it, you can do so portably with alias. The order of shell expansion enables this work-around. Because the alias is expanded while the command is being read, whatever you replace the set name with in your definition will expand correctly, it just probably should not expand to one of those names.

So you could do:

alias set=./set

...which will work just fine.

mikeserv
  • 58,310
3

The problem is that set is a shell builtin and the best solution would be to use a different name for your executable program.

Incidentally, last week, I asked a question on how to run system commands instead of shell builtins with the same name and the solution I accepted was to run the command through env:

env set 2 3

For this particular case, where you already know that the command you want to use is located in your current directory, it would be better to directly run the executable by entering its path (using . to represent the current working directory):

./set 2 3

Both the above solutions are shell agnostic, i.e., they will work regardless of which shell you’re using.

Suggestions such as using the command builtin won’t work in Bash: this only prevents shell functions from being run. While, it’s not documented, I’ve also noticed that using command also suppresses shell keywords. However, it won’t do the same for shell builtins such as set. As I understand it, command may work with other shells such as zsh.

Also, tricks such as \set or "set" or 'set' don’t work for Bash builtins – though they are useful for running executable files instead of aliases or shell keywords.

Note: This answer originally began as a comment on Eric’s (accepted) answer but grew too big to fit into a comment. The other answers recommending the use of type and not adding . to the PATH are good ones.