▶Prefer to watch? This short video runs this page's main commands on a real Ubuntu 24.04 machine, with on-screen captions and no voiceover. Click to play, or watch it on YouTube.
csh / tcsh [options] [script [arguments...]]
tcsh --version | echo $version
csh is the C shell, written by Bill Joy for BSD Unix. Its syntax looks like the C language (if (...) then, foreach, @ for math),
and it introduced ideas we now take for granted: history with !!, aliases and job control. tcsh ("TENEX C shell") is the modern,
enhanced csh: command-line editing, smart tab completion, a spelling corrector and a nicer prompt. On many systems /bin/csh IS tcsh.
Where you still meet it: BSD systems (FreeBSD, NetBSD, OpenBSD), older macOS, Solaris and other commercial Unix, and a lot of
engineering and chip-design (EDA) and scientific computing environments whose setup scripts are written as .cshrc files.
Use csh/tcsh for interactive work if you like it, but write scripts in sh or bash. Csh is a poor scripting language
(see "Why not to script in csh"). This sheet is written for tcsh, and notes where plain csh differs.
Install and switch
sudo apt install csh tcsh # Debian/Ubuntu sudo dnf install tcsh Fedora/RHEL family
pkg install tcsh # FreeBSD brew install tcsh macOS
tcsh # try it: start tcsh from your current shell. Type exit to go back
echo $version # tcsh prints its version (plain csh has no $version)
echo $shell # your login shell echo $tcsh tcsh's version number
which tcsh # where it is, for example /usr/bin/tcsh
echo /usr/bin/tcsh | sudo tee -a /etc/shells # allow it as a login shell (use the path from "which")
chsh -s /usr/bin/tcsh # make it YOUR login shell, then log out and in
chsh -s /bin/bash # go back
Command line options: tcsh -f (do not read startup files) -n (parse only, run nothing: a syntax check) -x (echo each command after history expansion)
-v (echo commands before expansion) -c 'command' (run one command) -e (exit on error) -l (act as a login shell)
Startup files (which one runs when)
/etc/csh.cshrc, /etc/csh.login System-wide (Linux; BSD uses /etc/csh.cshrc and /etc/csh.login too)
~/.tcshrc tcsh reads this first. If it does not exist, tcsh reads ~/.cshrc
~/.cshrc Read by EVERY csh/tcsh: login shells, new terminals, scripts and remote commands (ssh host cmd)
~/.login Login shells only, after .cshrc. Good for setenv and terminal setup
~/.logout Run when a login shell exits
~/.history Saved history (with savehist)
Put aliases, prompt and key bindings in ~/.tcshrc (or ~/.cshrc). Put environment setup in ~/.login or .cshrc.
IMPORTANT: .cshrc also runs for non-interactive shells (scp, rsh, ssh host command). Anything that prints text or needs a terminal there will break them.
Guard interactive-only lines:
if ($?prompt) then
# interactive only: aliases, prompt, bindkey, stty...
endif
Reload without logging out: source ~/.tcshrc
Variables: shell variables vs environment variables
set name = value # a shell variable (spaces around = are fine). Only this shell sees it
set name=value # also fine set name (empty variable)
echo $name echo ${name}_x # use it (braces mark where the name ends)
unset name # remove it
set # list all shell variables
setenv EDITOR vim # an ENVIRONMENT variable, passed to programs you start. NO = sign
setenv PATH "/opt/bin:$PATH" # it is a normal string here
unsetenv EDITOR # remove it
setenv # list environment variables printenv also lists them printenv HOME one value
set -r name = value # read-only (tcsh) set -f name = (a b) / set -l (rarely needed)
@ n = 5 + 3 # ARITHMETIC (integers). Spaces are required around operators
@ n++ @ n += 2 @ n = $n * 4 @ n = ($n + 1) / 2
echo $n # 12 etc.
set arr = (apple banana cherry) # a list (array). Parentheses and spaces
echo $arr[1] echo $arr[$#arr] # first and last (indexes start at 1)
echo $arr[2-3] echo $arr[*] # a slice / all items
echo $#arr # number of items
set arr = ($arr date) # append shift arr remove the first item
echo $?name # 1 if the variable "name" is set, 0 if not. (Use it to test whether a variable exists)
set line = "$<" # read one line from standard input (the keyboard)
echo "Enter your name:"; set name = "$<"
set today = `date +%F` # command substitution uses `backticks` only (no $(...) in csh or tcsh)
set files = `ls *.txt`
Beware: setenv and set are different. If you do "set EDITOR = vim" programs will not see it. Use setenv for anything a program should inherit.
Special variables:
$status Exit status of the last command (0 = success). NOT $? $$ PID of this shell $! PID of the last background job
$argv Arguments of the script or function-like alias: $argv[1], $1, $#argv, $argv[*], $*
$cwd Current directory $home Your home $user Your login name $host $hostname Machine name $HOST (tcsh)
$path A LIST tied to the PATH environment variable $term Terminal type $shell The shell $version (tcsh)
$uid $gid User and group IDs $ostype $machtype $vendor $hosttype Platform information (tcsh)
PATH (a common trip-up)
echo $path # a list, one entry per item
set path = ($path /opt/bin ~/bin) # add folders (this also updates the PATH environment variable automatically)
set path = (/usr/local/bin $path) # put one first
rehash # rebuild the command lookup table after installing a new program (tcsh does this if you set autorehash)
unhash # turn the hash table off
which mytool where mytool # tcsh builtins. "where" shows ALL matches (aliases, builtins and files)
Do not write "setenv PATH ..." AND "set path = ..." in different places, or one will overwrite the other. Pick set path.
Aliases (csh has aliases but NO functions)
alias ll 'ls -lah' # note: name, then the text in quotes (no = sign)
alias .. 'cd ..'
alias rm 'rm -i'
alias h history
alias # list all aliases alias ll show one unalias ll remove
\ls # run the real "ls", skipping any alias alias -l (tcsh) no expansion
Aliases with arguments use history-style markers:
alias mkcd 'mkdir -p \!^ && cd \!^' # \!^ = first argument \!* = all arguments \!:2 = second argument \!$ = the last one
alias cd 'chdir \!* && ls' # run ls after every cd
alias psg 'ps aux | grep -i \!*' # psg nginx
The backslash before ! matters, so the shell keeps the marker until the alias is used. There is no way to write a real function: for anything bigger use a script.
Special aliases (tcsh runs these automatically):
alias precmd 'printf "\033]0;%s@%s\007" $USER $HOST' # before each prompt (here: set the window title)
alias cwdcmd 'ls' # after every directory change
alias periodic 'echo tick' # every "tperiod" minutes (set tperiod = 30)
alias postcmd 'echo done' # before each command runs (after you press Enter)
alias helpcommand 'man' # what Esc-h does
The prompt
set prompt = '%n@%m:%~%# ' # user@host:directory% (single quotes so the codes reach the prompt logic)
%n user %m short hostname %M full hostname %~ current directory (with ~) %/ full path %c or %C last part(s) of the path
%t time (12 hour) %T time (24 hour) %p time with seconds %D %w %W %y date parts %h or %! history event number
%# ">" for a user and "#" for root %? exit status of the last command %j number of jobs
%B ... %b bold %U ... %u underline %S ... %s standout (reverse video) %{ ... %} text that takes no room (for color codes)
%N.M ... variable-width %$var the value of a variable %<...< text to use when the path is truncated
Colors (set inside %{ %}):
set red = "%{\033[1;31m%}"
set green = "%{\033[1;32m%}"
set blue = "%{\033[1;34m%}"
set reset = "%{\033[0m%}"
set prompt = "${green}%n@%m${reset}:${blue}%~${reset}%# "
set prompt2 = '%R? ' # the continuation prompt set prompt3 = 'CORRECT> %R (y|n|e)? '
set rprompt = '%T' # a prompt on the RIGHT side of the screen (tcsh only)
Plain csh only understands set prompt = "hostname% " (and ! for the history number). The % codes above are tcsh.
For a themed prompt with git status and more, see the Oh My Posh sheet.
History
history # list history (tcsh numbers each entry) history 20 the last 20 history -r reverse
!! # repeat the last command sudo !! repeat it with sudo
!$ # the last WORD of the last command mkdir /tmp/x ; cd !$
!^ # the FIRST argument of the last command (!:1 is the same) !* all arguments !:2 the second word
!n # run history entry number n !-2 two commands back
!vi # the last command that started with "vi" !?text? the last one containing "text"
^old^new # repeat the last command, replacing old with new !!:s/old/new/ the same with :s
!!:p # print the command without running it (then use !! to run it)
Modifiers (add after the word selector): :h (head: remove the last path part) :t (tail: file name) :r (remove the extension) :e (extension only)
:p (print only) :q (quote it) :s/old/new/ (substitute) :gs/old/new/ (substitute everywhere) :u / :l upper / lower case (tcsh)
Example: vi /etc/hosts.allow then cp !$ !$:t.bak (copies to hosts.allow.bak in the current folder)
set history = 5000 # how many entries to keep in memory
set savehist = (5000 merge) # how many to save in ~/.history at logout, and merge with other sessions
set histdup = erase # drop duplicate entries (also: prev, all)
set histfile = ~/.history_tcsh
history -S history -L history -M # save / load / merge the history file now (tcsh)
history -c # clear history (tcsh)
"Event not found" errors: the ! character starts history expansion even INSIDE double quotes. Escape it with a backslash: echo Hello\! or use set histchars
Keyboard editing and completion (tcsh, emacs style by default)
Ctrl+A / Ctrl+E Start / end of the line Ctrl+B / Ctrl+F Back / forward one character
Alt+B / Alt+F Back / forward one word Ctrl+W (or Alt+Backspace) Delete the word before the cursor
Ctrl+U Delete the whole line (tcsh: to the start) Ctrl+K Delete to the end of the line
Ctrl+Y Paste what you deleted Ctrl+L Clear the screen
Ctrl+R Search history backward as you type (incremental search) Ctrl+S search forward
Alt+P / Alt+N Search history for lines that START with what you typed (press Esc, then p or n)
Ctrl+D On an empty line: exit (unless ignoreeof). In the middle: list completions or delete a character
Alt+. (Esc .) Insert the last argument of the previous command Alt+/ Complete a word from the history
Tab Complete a command, file, directory or variable name Ctrl+D List the possible completions
Alt+? / Alt+H Show the man page for the command you are typing Alt+S Correct the spelling of the word (tcsh)
Ctrl+C Stop the running command Ctrl+Z Suspend it (resume with fg or bg)
bindkey List every key binding bindkey -v Use vi-style keys bindkey -e Back to emacs-style keys (the default)
Make the Up and Down arrows search by prefix (add to ~/.tcshrc):
bindkey "^[[A" history-search-backward # Up
bindkey "^[[B" history-search-forward # Down
bindkey "^[OA" history-search-backward # some terminals send this for Up
bindkey "^[OB" history-search-forward
bindkey "^[[3~" delete-char # make the Delete key work
bindkey "^[[H" beginning-of-line # Home
bindkey "^[[F" end-of-line # End
bindkey "^[[1;5C" forward-word # Ctrl+Right bindkey "^[[1;5D" backward-word # Ctrl+Left
Find the code for a key: run bindkey and press Ctrl+V then the key, or use cat -v and press it.
Completion options (tcsh):
set autolist # show the list of choices when a completion is ambiguous (instead of a beep)
set autolist = ambiguous # only after a second Tab when nothing more can be completed
set complete = enhance # ignore case, and treat - . _ as word separators when completing
set correct = cmd # offer to fix a misspelled COMMAND ("CORRECT>ls (y|n|e|a)?") correct = all fixes arguments too
set autocorrect # fix simple typos in a word before completing
set recognize_only_executables # only complete commands that are actually executable
set fignore = (.o .bak '~') # do not offer these file endings when completing
set filec # plain csh: enable file name completion (Esc completes, Ctrl+D lists)
Custom completion with complete (examples):
complete cd 'p/1/d/' # the first word after cd completes as a DIRECTORY
complete rmdir 'p/*/d/' # every word as a directory
complete unset 'p/1/s/' # shell variable names
complete unsetenv 'p/1/e/' # environment variable names
complete alias 'p/1/a/' # alias names
complete man 'p/*/c/' # command names
complete kill 'c/-/S/' # after "kill -" complete signal names
complete ssh 'p/1/$hosts/' # after: set hosts = (server1 server2 ...)
Use "uncomplete name" to remove one, and "complete" with no arguments to list them.
Startup options worth setting (~/.tcshrc)
set noclobber # > will not overwrite an existing file (use >! to force it)
set ignoreeof # Ctrl+D does not log you out (you must type logout)
set notify # tell me the moment a background job finishes
set nobeep # no bell
set autorehash # find newly installed programs without typing rehash
set implicitcd # type a directory name to change into it
set symlinks = chase # cd through symbolic links to the real directory (or ignore, expand)
set listjobs = long # print job details when you press Ctrl+Z
set printexitvalue # print "Exit 1" when a command fails
set rmstar # ask before "rm *" (a good safety net)
set color # colors for ls-F and the spelling corrector set colorcat color for messages
set watch = (1 any any) # tell me when anyone logs in or out set who = "%n has %a %l from %M"
set mail = (/var/mail/$USER) # check mail
set cdpath = (~ ~/projects) # cd looks in these folders for a relative name (like CDPATH)
set autoexpand # expand history and variables with the Tab key
set echo_style = both # make echo understand both -n and backslash escapes
set nonomatch # a wildcard that matches nothing stays as the plain word (no "No match" error)
set noglob # turn wildcards off (turn on again with unset noglob)
alias ls 'ls-F' # tcsh's built-in ls with file type marks (or use ls -F)
Directories
cd /var/log cd cd ~ cd - # change directory / home / home / previous
pushd /tmp popd dirs -v # a directory stack: pushd goes and remembers, popd goes back
pushd +2 # rotate to the third entry on the stack
set dunique # keep the stack free of duplicate entries
pwd dirs # where am I?
ls -lah ls -ltr ls -d */ # long, hidden, human sizes | oldest first, newest last | directories only
mkdir -p a/b/c # create parents rmdir dir remove an empty directory
~user ~/projects =2 # (tcsh) =2 stands for the second entry in the directory stack
Wildcards (globbing) and quoting
* ? [abc] [a-z] {a,b,c} # * any characters, ? one character, brackets a set, braces a list of alternatives
ls *.txt ls file?.log ls [A-C]* ls file{1,2,3}.txt mkdir -p project/{src,docs,tests}
'single quotes' # literal text (variables are NOT expanded), but ! still triggers history
"double quotes" # $variables and `commands` still expand
\$ \! \\ # backslash escapes one character
echo 'It'"'"'s' # quoting a single quote is awkward in csh: use double quotes when the text has an apostrophe
Errors: "No match" means the wildcard found no files (csh stops the command). Use set nonomatch to leave it as text.
Redirection and pipes (this is different from bash: there is NO 2> in csh)
cmd > file # stdout to a file (overwrite) cmd >> file append
cmd >& file # stdout AND stderr to a file cmd >>& file append both
cmd >! file cmd >>! file # override noclobber (force overwrite / append even if the file is missing)
cmd < file # stdin from a file
cmd1 | cmd2 # pipe stdout cmd1 |& cmd2 pipe stdout AND stderr
(cmd > out.txt) >& err.txt # the csh way to send stdout and stderr to DIFFERENT files (a subshell)
cmd >& /dev/null # throw everything away cmd > /dev/null throw away just stdout
cmd | tee file # show and save cmd |& tee file include errors
cat <<EOF # here-document (the shell expands $variables in it)
Hello $user
EOF
cat <<'EOF' # quoted marker: no expansion
Literal $text
EOF
cmd1 && cmd2 cmd1 || cmd2 cmd1 ; cmd2 # run the second only on success / only on failure / always
cmd & # run in the background
If you want bash-style 2>&1 and 2>file redirection, csh cannot do it: run the command through bash: bash -c 'cmd 2> err.txt'
Control flow (scripts and the command line)
if ($count > 10) echo "big" # one-line if. The condition MUST be in parentheses
if ($count > 10) then
echo "big"
else if ($count > 5) then
echo "medium"
else
echo "small"
endif
switch ($answer)
case y:
case Y:
echo "yes"
breaksw
case n*:
echo "no"
breaksw
default:
echo "unknown"
breaksw
endsw
foreach f (*.txt) # loop over a list. Parentheses are required
echo $f
end
foreach i (`seq 1 5`)
echo $i
end
foreach word ($arr)
echo $word
end
set i = 1
while ($i <= 5)
echo $i
@ i++
end
repeat 3 echo "hi" # a builtin: run a command N times
break continue # leave the loop / skip to the next round
goto done # csh has goto (it works, but avoid it)
done:
exit 0 # end with a status
Conditions and tests:
== != < > <= >= && || ! # numeric or string comparison, and, or, not
$name =~ a* $name !~ a* # glob pattern match (unquoted pattern) and non-match
-e file (exists) -f (regular file) -d (directory) -r (readable) -w (writable) -x (executable) -s (not empty) -z (empty) -l (symlink) -o (you own it)
if (-e /etc/hosts && -r /etc/hosts) echo "ok"
if ("$name" == "") echo "empty" # quote possibly-empty variables
if ($status == 0) echo "success" # exit status of the last command
if ({ grep -q foo file }) echo "found" # braces run a command and test its success (1 = success)
if ($?var) echo "var is set" # is a variable defined?
if ($#argv == 0) then # script with no arguments
echo "Usage: $0 file"
exit 1
endif
Csh needs spaces around operators and inside the braces. "if ($a==$b)" fails. Write "if ($a == $b)".
Jobs and processes
command & # run in the background Ctrl+Z suspend the foreground job
jobs jobs -l # list jobs (with PIDs)
fg fg %2 bg bg %1 # bring to the foreground / resume in the background
stop %1 # suspend a background job kill %1 kill by job number kill PID by process ID
kill -9 PID kill -HUP PID # 9 cannot be caught, HUP means hangup / reload
notify %1 # tell me when this job finishes (or set notify for all)
nohup command & # keep running after you log out (csh has a builtin nohup)
nice +10 command # lower priority (csh builtin: positive = less CPU) nice -10 command raise priority (root)
time command # how long it took (tcsh formats it with $time) set time = 5 automatically time commands taking over 5 seconds
sched 11:30pm echo "bed time" # (tcsh) schedule a command for later inside the shell
limit # show resource limits limit coredumpsize 0 no core files limit descriptors 4096 unlimit remove limits
umask 022 # default permissions for new files
ps aux | grep '[n]ginx' # find processes (the [n] trick keeps grep out of the list)
onintr label # in a script: what to do on Ctrl+C: onintr cleanup ... cleanup: rm -f /tmp/x$$ ; exit 1
A csh script (and its limits)
#!/bin/csh -f
# The -f means "do not read ~/.cshrc": this makes the script faster and predictable. Use #!/bin/tcsh -f for tcsh features.
if ($#argv < 1) then
echo "Usage: $0 file [file...]" >& /dev/stderr
exit 2
endif
set logfile = /tmp/run.$$.log # $$ is the process ID: a unique name
onintr cleanup # run "cleanup" if the user presses Ctrl+C
foreach f ($argv)
if (! -f "$f") then
echo "Skipping $f (not a file)"
continue
endif
set lines = `wc -l < "$f"`
@ total += $lines
echo "$f has $lines lines" >> $logfile
end
echo "Total: $total lines"
rm -f $logfile
exit 0
cleanup:
echo "Interrupted"
rm -f $logfile
exit 1
chmod +x script.csh then ./script.csh args Syntax check without running: tcsh -n script.csh Trace it: tcsh -x script.csh
Debug a line: put "set echo" and "set verbose" at the top. Arguments: $argv[1], $1, $#argv, $argv[*].
Why not to script in csh (read this before you start a big script)
- No functions: only aliases and goto. You cannot build reusable pieces
- You cannot redirect stderr separately from stdout inside a command (no 2>), except with the subshell trick
- Quoting and parsing are quirky: parentheses, ! history expansion, and word splitting cause odd errors
- Loops and if blocks cannot be redirected or piped as a whole: foreach ... end | sort does not work
- No here-doc control, no arrays of arrays, weak string tools, limited signal handling (only onintr), errors that stop the whole shell
- Everything you find online (and most sysadmin books) assumes sh or bash. Bash is on nearly every Linux system
A classic essay makes the case: "Csh Programming Considered Harmful" (Tom Christiansen). Use csh/tcsh interactively and write scripts in sh or bash.
Need to run a bash script from csh? bash script.sh Need one bash line? bash -c 'for i in 1 2 3; do echo $i; done'
Bash to csh: translation table
bash csh / tcsh
name=value set name = value
export NAME=value setenv NAME value
unset NAME unset name (shell variable) or unsetenv NAME (environment variable)
$(command) or `command` `command`
echo $? echo $status
echo "$1" "$@" echo "$1" $argv[*] $* (in a script)
alias ll='ls -l' alias ll 'ls -l'
function f { ... } (no functions: use an alias with \!*, or a script)
for i in 1 2 3; do ...; done foreach i (1 2 3) ... end
while [ cond ]; do ...; done while (cond) ... end
if [ cond ]; then ...; fi if (cond) then ... endif
case $x in a) ...;; esac switch ($x) case a: ... breaksw ... endsw
[ -f file ] [[ -f file ]] -f file (inside if ( ))
$((1 + 2)) @ n = 1 + 2
read -r line set line = "$<"
cmd > file 2>&1 cmd >& file
cmd &> file cmd >& file
cmd 2> err (cmd > /dev/tty) >& err (see Redirection) or use bash -c
cmd1 |& cmd2 (bash 4) cmd1 |& cmd2
PATH=$PATH:/opt/bin set path = ($path /opt/bin)
source file.sh source file.csh (bash syntax will not work)
history expansion inside double quotes also active in csh (escape with \!)
trap 'cmd' INT onintr label
set -e / set -x set echo / tcsh -x (there is no -e for scripts, use "tcsh -e" or #!/bin/csh -ef)
${var:-default} if ($?var) then ... else set var = default endif
Importing environment from a bash setup script:
bash -c 'source ./env.sh && env' | sed -n 's/^\([A-Za-z_][A-Za-z0-9_]*\)=\(.*\)$/setenv \1 "\2";/p' > /tmp/env.csh ; source /tmp/env.csh (fragile: check the result first)
Practical one-liners
# Add a folder to your PATH for good (in ~/.tcshrc)
set path = ($path ~/bin /opt/tools/bin)
# Run a command on every file
foreach f (*.jpg)
convert $f `echo $f:r`.png
end
(or on one line: foreach f (*.jpg); convert $f $f:r.png; end)
# Rename files: .txt to .bak
foreach f (*.txt)
mv $f $f:r.bak
end
# Modifiers on variables: $f:r (no extension), $f:e (extension), $f:t (file name), $f:h (directory)
set f = /var/log/app.log; echo $f:t $f:h $f:r $f:e
# Count files
@ n = `ls | wc -l`; echo $n
# Ask a question
echo -n "Continue? [y/n] "; set ans = "$<"; if ("$ans" !~ [yY]*) exit
# Set the window title to user@host:directory (in ~/.tcshrc, guarded for interactive shells)
if ($?prompt && $?TERM) then
alias cwdcmd 'printf "\033]0;%s@%s: %s\007" $USER $HOST $cwd'
endif
# Redirect stdout and stderr to different files
(find / -name core > found.txt) >& errors.txt
# Show which shell is running and whether you are in a login shell
echo $shell $version; echo $?loginsh
# Kill every process named firefox
kill `pgrep firefox`
# Run a long job, and email when it finishes (mail must be set up)
(long_job ; echo "done" | mail -s "job finished" $USER) &
# Use bash for a one-liner that needs 2>
bash -c 'command 2>/dev/null'
# Show all environment variables containing PATH
printenv | grep PATH
# List aliases matching a word
alias | grep git
Common problems
"Undefined variable." You used $name before setting it. Test with if ($?name) first, or set a default
"Missing }." / "Missing )." A bracket is missing. Check ( ) around conditions and { } around commands
"Badly placed ()'s." / "Too many )'s" A parenthesis error, often in an if or foreach. Csh needs foreach f (...) and if (...) then
"if: Expression Syntax." Spaces missing around operators, or an unquoted empty variable. Write if ("$a" == "$b")
"Ambiguous output redirect." Two redirections to the same stream. Rewrite with a subshell or use bash -c
"No match." A wildcard found nothing. Use set nonomatch or check the folder
"Event not found." / "Event not found: ..." An ! in your text started history expansion. Put a backslash before it: \!
"Unmatched '." A quote is not closed, often an apostrophe in text. Use double quotes
"Word too long." / "Too many words" Very long lines or huge wildcard expansions. Use xargs or find
"Illegal variable name." / "Variable name must contain alphanumeric characters" You wrote $? or $1x. Use $status, and ${1}x
"set: Syntax Error" for set a=$b with spaces Put spaces the csh way: set a = $b (and quote if the value has spaces)
"setenv: Too many arguments" Quote values that contain spaces: setenv MSG "hello world"
"echo: No match" for echo *.txt in an empty folder Same as No match above
Command not found after installing something Run rehash (or set autorehash in ~/.tcshrc)
A program cannot see my variable You used set instead of setenv
PATH is wrong after login Two files set it (~/.cshrc and ~/.login). Keep one place, and use set path = ($path ...)
ssh host command prints odd output or fails Your ~/.cshrc prints text or runs terminal commands for non-interactive shells. Wrap them in if ($?prompt) then ... endif
Backspace or arrow keys print ^H or ^[[A Fix the key bindings with bindkey (see above) and check stty erase ^?
Arrow Up does not search by prefix Add the bindkey lines for ^[[A and ^[[B to ~/.tcshrc
Tab completion does not work In plain csh set filec. In tcsh check that it really is tcsh: echo $version
Script says "Command not found" for the shebang The interpreter path is wrong, or the file has Windows line endings: dos2unix script.csh
Script runs but ignores my changes It reads ~/.cshrc first. Add -f to the shebang: #!/bin/csh -f
Copy and pasted bash commands fail Bash and csh syntax differ (export, 2>&1, $(...), functions). Run them with bash -c, or translate using the table above
Tips
Use tcsh, not plain csh, for anything interactive: the editing, completion and prompt features are worth it.
Use setenv for variables programs must see, and set for shell-only variables. Confusing them is the top beginner mistake.
Guard everything interactive in ~/.cshrc with if ($?prompt) then ... endif so ssh, scp and scripts keep working.
Use "set path = ($path ...)" for PATH changes, and run rehash after installing new programs.
Remember there is no 2> in csh. Use >& for both streams, the (cmd > out) >& err trick for separate files, or run the command through bash -c.
In csh, ! triggers history expansion even inside double quotes. Escape it as \! when you mean an exclamation mark.
Write real scripts in bash or sh, not csh. Start any csh script with #!/bin/csh -f so it does not read your ~/.cshrc.
Turn on set noclobber, set rmstar and set correct = cmd as safety nets, and set autolist and set savehist to make daily use smoother.
If a legacy tool (EDA, cluster, vendor software) ships a setup.csh, source it from csh or tcsh. Or run it in bash with a wrapper that imports the variables.
Type man tcsh for the complete manual, and try things in a spare tcsh session before you make it your login shell.