I've noticed that tcsh, regardless of whether "-f" flag is passed on the shebang line, will iterate through $PATH, and try to execute the command from that path until the command is found. Whereas bash first checks whether the command is present in that location.

This tcsh behaviour leads to a lot of failed entries in the audit logs as our audit has been configured to capture execve system calls. For instance when sleep is called from within a tcsh script, one of the failed audit entries shows it tried to run sleep with absolute path: "/usr/local/bin/sleep".

type=SYSCALL msg=audit(1710330471.326:37838): arch=c000003e syscall=59 success=no exit=-2 a0=2601590 a1=261e010 a2=261d110 a3=7ffdc4a409e0 items=1 ppid=8930 pid=8938 auid=1011478343 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts1 ses=863 comm="csh_test.sh" exe="/usr/bin/tcsh" subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key="non_sys_execs"

type=CWD msg=audit(1710330471.326:37838): cwd="/tmp"

type=PATH msg=audit(1710330471.326:37838): item=0 name="/usr/local/bin/sleep" objtype=UNKNOWN cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0

type=PROCTITLE msg=audit(1710330471.326:37838): proctitle=2F62696E2F637368002E2F6373685F746573742E7368

Strace shows bash stats first, before attempting an access:

stat("/usr/local/bin/sleep", 0x7ffdd8de5630) = -1 ENOENT (No such file or directory)
stat("/usr/local/sbin/sleep", 0x7ffdd8de5630) = -1 ENOENT (No such file or directory)
stat("/sbin/sleep", 0x7ffdd8de5630)     = -1 ENOENT (No such file or directory)
stat("/bin/sleep", {st_mode=S_IFREG|0755, st_size=33128, ...}) = 0

The order in which it traverses the paths matches the order the paths are defined in $PATH.

Unfortunately changing the order around on all our servers will not be possible, also it's unclear what the implications would be of putting /bin/ ahead of /usr/local/bin etc. Whilst, I doubt it, but just in case, is there perhaps some runtime or install configuration to force tcsh to stat first just like bash does?

Is there anything else that could be done at all to avoid these failures (apart from amending scripts to use the full path or filtering out these execve calls from audit capture)?

  • Is the audit message any different for trying to execute a file that isn't there, vs. a file where e.g. the user doesn't have execute permission? I.e. can it tell ENOENT from EACCES? (I'm not exactly sure from the output.) Because I'm wondering if those audit messages are something it makes even sense to collect in the first place, regardless of what tcsh does. And, well, I think just trying execve() makes sense in that that's what the shell is actually trying to do. Calling stat() first just be a useless extra step. (And that goes regardless of if the shell caches the command location.)
    – ilkkachu
    Mar 15 at 11:05
  • (btw, you can produce bolded monospace text with <code><b>foo</b></code>, or <pre><b>foo</b></pre> for a whole block. I hope I didn't mess the content up with the edit)
    – ilkkachu
    Mar 15 at 11:06
  • Thanks for the tip! Yes, the audit messages are different. It is possible to exclude just these type of entries by filtering out on the exe value and exit status, with the downside ofcourse of the performance hit of an extra two rules. Ideally though I'd like to prevent these execve calls in the first place. Given that bash, and also tcsh it seems, hash the path of the executables I'm actually quite surprised to find they still traverse $PATH for some reason.
    – Maikol
    Mar 15 at 11:16
  • 1
    Is there a strong reason for using tcsh at all? Mar 15 at 11:26
  • 1
    It seems my previous response didn't get saved. Just wanted to thank you all for your contributions. I'm looking to enable command hashing from within the script itself. I've tried adding rehash to the top of the script, this has reduced the number of failed execve calls per command from 5 to 2. For some reason it still tries to execute the script from "/sbin/" first.
    – Maikol
    Mar 17 at 22:35

1 Answer 1


The trick was to force tcsh to enable command hashing, which as it turns out get's disabled if it's called with "-f" flag, which was the case in my problematic scripts.

At the top of the script I just added:

rehash 4096

See the answer in: Enabling command hashing in tcsh, for a very detailed description of command hashing in tcsh.

  • For those who won't read the other answer in the link, it's important to point out that the parameters for rehash builtin are undocumented, and their existance can only be found in the tcsh source code. "Officially", only rehash is supposed to enable hashing and should not have parameters. The number is kind f a hack (that works, for now) but I'm not sure if it's supported or if it will stay in future releases.
    – aviro
    Mar 20 at 8:46

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .