#!/bin/csh -f # tridentcd # ------------------------------------------------------------------------- # C shell script to login to a remote server, cd to the specified folder, # and execute the optional specified command, defaulting to an interactive # shell. # ------------------------------------------------------------------------- # Revision History: # $Log$ # ------------------------------------------------------------------------- # Collect command line options set remotepath = "" set option_relative = "false" while ($#argv > 0) if ("$1" == "-h" || "$1" == "--help") then # Note: We're using a lot of different quote styles here # - Single quotes to suppress $variable evaluation and to show # double quote chars # - Double quotes to force $variable evaluation and to show # single quote chars # If necessary, we could also use: # - Backslash, which works as an "escape char" to show quote # chars and suppress variable evaluation, but only outside # of quotes, but not inside double quotes or inside single # quotes) # - Expanded and non-expanded "here docs" with the cat command # instead of echo. When used with "EOF" instead of EOF, # $variable expansion is suppressed. For example: # echo -n " $0:t" # cat <<"EOF" # -P '$JAVA_HOME' # "EOF" echo "Use ssh to open a shell, or run commands, in a folder (path)" echo "on a remote server" echo "Usage: $0:t [option...] [command...]" echo "Options:" echo " -h = Show this help text" echo " --help = Show this help text" echo " -P path = Remote path to cd to" echo " --path path = Remote path to cd to" echo " - The default remote path (when this option is" echo " NOT used) is a relative form of the current" echo " local path. This is convenient if the same" echo " relative path exists remotely as locally." echo ' The relative path is obtained from $PWD with' echo ' any prefix matching $HOME/ removed. So, it' echo " contains any symlinks that you used on your cd" echo " command. It does NOT expand symlinks to match" echo " the true location of the path. This is" echo " convenient if you intentionally used symlinks" echo " to make the local path match the desired" echo " remote path." echo " It DOES correct the upper/lower case of the" echo " path to match the true case of the folders and" echo " symlinks. This if convenient if you got the" echo " case wrong on your cd command on a Mac or" echo " some other system that tolerates case" echo " mismatches." echo " - If you DO use this option to specify a remote" echo " path, be careful about upper/lower case." echo " Even if case doesn't matter locally, it might" echo " matter remotely. If you derive the value to" echo " specify here from the current local path, note" echo ' that $PWD, $cwd, and the pwd command all' echo " return the case you specified on your cd" echo " command. The pwd -P command, on the other" echo " hand, corrects the case to what actually" echo " exists in the local filesystem. But it also" echo " strips out all symbolic links that you may" echo " have used on your cd command. That may be" echo " undesirable, if the local path with the" echo " symlinks is a better match for the remote" echo " path than it is without the symlinks. You" echo ' can filter the value of $PWD by calling' echo " normalize_filename to get a value with the" echo " case corrected. But it may be easier to just" echo " use the --relative option below, which does" echo " that automatically." echo " - Enclose path in single quotes ('') or escape" echo ' with backslashes (\) to evaluate ~ and' echo ' $variable names like $HOME remotely instead' echo " of locally." echo " --relative = Make path relative to user's home directory" echo ' - Strip $HOME/ (if present) from the front of' echo " the specified or default path, to convert" echo " absolute local paths to relative local paths" echo " which are presumably also valid relative" echo " remote paths. Also, call normalize_filename" echo " to correct the upper/lower case of the path." echo " - This option is implied when -P or --path is" echo " NOT used. In that case, it maps the current" echo ' local path ($PWD) to the same relative path' echo " remotely. Probably the most common use case" echo " is the default of NOT explicitly using any of" echo " these options." echo " - The 2nd most common use case is probably to" echo " explicitly use -P/--path but not this option." echo " In that case the path you specify is used" echo " exactly as you specified it." echo " - It's useful to specify this option explicitly" echo " when you have no convenient way to specify a" echo " relative path, and you resort to using the" echo ' local $PWD in the value of the -P or --path' echo " option." echo "Args:" echo " command = Command(s) to execute on the remote server" echo " - Can be a quoted string with wildcards, variable" echo " expansion, multiple commands separated by" echo " semicolons (;), AND (&&), OR (||), pipe (|)," echo " redirection (<, >, etc.) and so on. Anything" echo " supported by the ssh command." echo " - Defaults to an interactive shell" echo " - To pass a quoted string as an arg to the command," echo " enclose it in both single and double quotes, as" echo " you'd have to do with the ssh command." echo "Examples:" echo " $0:t" echo " - Open shell in current folder on remote server" echo " (The default shell type of the remote user)" echo " $0:t -P sub1/sub2" echo " $0:t --path sub1/sub2" echo " - Same in sub1/sub2 subfolder of current folder" echo "" echo " $0:t ls -FlA" echo " - Run ls -FlA command in current folder on remote server" echo " $0:t 'ls -FlA; rm -riv sub3; ls -FLA'" echo " $0:t "'"ls -FlA; rm -riv sub3; ls -FLA"' echo " - Run 3 commands in current folder on remote server" echo " $0:t -P sub1/sub2 "'"ls -FlA; rm -riv sub3; ls -FLA"' echo " - Same in sub1/sub2 folder of current folder" echo "" echo " $0:t vi file1.txt" echo " - Open file1.txt in interactive vi editor on remote server" echo " $0:t -P sub1/sub2 vi file1.txt" echo " - Same in sub1/sub2 folder of current folder" echo "" echo " $0:t -P "'$JAVA_HOME' echo " $0:t -P "'"$JAVA_HOME"' echo ' - Open shell in $JAVA_HOME folder on remote server' echo ' ($JAVA_HOME evaluated locally. Relative path must exist remotely.)' echo " $0:t -P '"'$JAVA_HOME'"'" echo ' - Open shell in $JAVA_HOME folder on remote server' echo ' ($JAVA_HOME evaluated remotely because of single quotes)' echo "" echo " $0:t 'ls -FlA "'$JAVA_HOME/bin'"'" echo " $0:t ls -FlA '"'$JAVA_HOME/bin'"'" echo " $0:t ls -FlA '"'$JAVA_HOME'"'/bin" echo " - Run command on remote server" echo ' ($JAVA_HOME evaluated remotely in all 3 cases)' echo "" echo " $0:t '"'echo "$USER ($uid) $GROUP ($gid)"'"'" echo " - Run command on remote server" echo ' (Various $vars and $ENVVARS evaluated remotely)' echo "" echo " cd ~/abc; $0:t ls" echo " - Run command in remote ~/abc from local ~/abc" echo " cd ~/xyz; $0:t ls abc" echo " - Same effect without ever cd'ing to abc remotely" echo " (Fails if remote ~/xyx does not exist)" echo " cd ~/xyz; $0:t -P abc ls" echo " - Same by specifying relative remote path explicitly" echo " cd ~/xyz; $0:t -P '"'$HOME/abc'"' ls" echo " - Same by specifying absolute remote path explicitly" echo " cd ~/xyz; $0:t -P "'$HOME/abc'" --relative ls" echo " - Specifying absolute local path converted to relative" echo " cd ~/xyz; $0:t -P "'$HOME/abc'" ls" echo " - Specifying absolute local path not converted to relative" echo " (Fails unless local and remote absolute path match)" echo "" echo " cd ~/xyz; $0:t 'cd abc && ls'" echo " - Explicitly passing relative cd command to server" echo " (Fails if remote ~/xyx/abc does not exist)" echo " cd ~/xyz; $0:t 'cd "'$HOME/abc'" && ls'" echo " - Explicitly passing absolute cd command to server" echo " cd ~/xyz; $0:t "'"cd $HOME/abc && ls"' echo " - Passing cd command with absolute local path to server" echo " (Fails unless local and remote absolute path match)" echo "" echo " $0:t -P /var/log/httpd 'grep exist error_log* | grep -v /var/www/html | grep -v public_html | wc -l'" echo " - Show me count of web server errors for files not existing" echo " other than those in public and personal web sites" echo " (Pipes and wildcards work fine)" echo " $0:t -P /var/log/httpd 'grep exist error_log* | grep -v /var/www/html | grep -v public_html | less'" echo " - Show me the errors that were counted" echo "" echo " $0:t grep "'"'"'GET /Tips'"'"'" access_log" echo " - Remotely do: grep 'GET /Tips' access_log" echo " $0:t grep '"'"'"GET /Tips"'"'"' access_log" echo ' - Remotely do: grep "GET /Tips" access_log' echo "" exit 1 else if ("$1" == "-P" || "$1" == "--path") then shift if ($#argv == 0) then beep "Error: Missing argument for option -P or --path at end of command" $0:t --help exit 1 endif set remotepath = "$1" shift if ("$remotepath" == "") then beep "Error: Empty argument for option -P or --path." $0:t --help exit 1 endif # Note: Don't wrap backticks in double quotes. $remotepath already # expands correctly without them. Adding them causes a problem # when the value of $remotepath contains a variable name that # is intended to be evaluated remotely. Instead, a call like: # tridentcd -P '$SYBASE' ls -FlA # can report an error here like: # SYBASE: Undefined variable set char1 = `echo $remotepath:q | cut -c 1` if ("$char1" == "-") then beep "Error: Missing argument for option -P or --path before '$remotepath'." $0:t --help exit 1 endif else if ("$1" == "--relative") then shift set option_relative = "true" # Note: Need '' around '$1:q' in case the user passed in a variable to # be evaluated remotely, prefixed with $, as the value of path. # Don't want that variable to be evaluated by the echo command. # Just want the $variable name passed to cut. else if ("-" == "`echo '$1:q' | cut -c 1`") then echo "Error: Invalid option: $1:q" $0 --help exit 1 else # Not a recognized option. Assume it's the first argument break endif end if ("$remotepath" == "") then # Note: Use pwd or echo $cwd or echo $PWD, not pwd -P, to capture # remotepath because pwd -P shows the real physical path to the # current directory, while the others show the symlinked path used # on the cd command. Want the symlinked path here because that # same symlinked path is likely to exist on the remote server. # If not, that can likely be arranged by the user for cases where # doing so can help to make the local and remote paths match better. set remotepath = "$PWD" # Plan to strip off $HOME/ and normalize the path since it was obtained # from $PWD. set option_relative = "true" else # Note: Do not necessarily plan to strip off $HOME and normalize the path. # The user specified the path explicitly, so assume it's correct. endif if ("$option_relative" == "true") then # Normalize the path to make sure the upper/lower case is correct. # The pwd command, $cwd and $PWD all return the case used on the # cd command, which may not be correct, but which cd tolerates. # Also, strip off $HOME to get relative pathname. See Usage above. # Notes: # - Use double quotes (""), not single quotes (''), around the entire # expression assigned to remotepath. This is to cause local expansion # of $remotepath and $HOME, despite them being enclosed also in single # quotes (''). If we switched to single quotes here, they would not # be expanded, even if we put double quotes around them individually. # - Use backticks (``) to execute the enclosed pipe of commands. # - Use single quotes ('') around $remotepath in case its value # contains a $variable to be evaluated remotely. Otherwise, that # $variable is evaluated locally by the backticks and passed to echo. # - In both sed commands: # - Use single quotes around the value of the sed -e argument (the # substitute command) because we're already using double quotes # around the entire expression and can't nest multiple sets of # double quotes. # - Use * instead of / to delimit search string and replacement # string, to avoid conflict with / in path. Since * is a # wildcard, it should never occur in a normal path. # - Use ^ to match value of $HOME only at the start of the path. # - Use backslash (\) to escape the dot (.) to prevent it being # treated as the "any single char" regular expression pattern. # - In the 1st sed command (paths starting but not ending with $HOME): # - Replace $HOME/ with explicit "./" # - Do not simply delete $HOME/ as: s*^$HOME/** # That would fail when the entire path was $HOME without a "/". # It would do no substitution at all. # Note: This case is now covered by the 2nd sed, so it would be # OK to simply delete $HOME after all, but I prefer this # because ./xyz is more obvious than simply xyx. # - Do not simply replace $HOME with "." as: s*^$HOME*\.* # That would fail for user xyz operating in the home directory of # user xyzpdq. It would map to the relative ./pdq folder of user # xyz, not to the absolute home directory of user xyzpdq. # - In the 2nd sed command (paths consisting entirely of $HOME): # - Use $ after $HOME to match value of $HOME only at the end of the # path # - Use single quotes ('') around that $ to prevent attempted $variable # substitution. The $ is part of the regular expression, not an # indicator a shell $variable # - Note that the 1st double quoted string ends just before the '$'. # It is automatically concatenated with '$' and with the "*\.*'`" # string that follow it. The resulting concatenation is the value # assigned to remotepath. This allows me to NOT have the $ inside # double quotes, which would have insisted on attempting $variable # substitution. # - Replace $HOME with explicit "." for 2 reasons: # - Do not simply delete $HOME resulting in the empty string. # - That would generate a cd command with no argument. # On some target servers (Linux/Unix.Mac), that moves to the # home directory as desired. But on other target servers # (DOS/Windows), it does the non-standard behavior of echoing # the current directory like a pwd command and moving nowhere. # Since we're already in the right directory after ssh'ing to # the remote server, either of those works fine, but the # Windows case generates undesired output. # - The command "cd ." is more obvious than simply "cd". # - In both sed commands: # - Do not hardcode to know the local and remote conventions about # locations of user home directories, as: s*/Users/*/home/* # That works for local Mac users with home directories in /User, # and remote Linux users with home directories in /home, but does # NOT work in cases like: # - Other Linux/Unix conventions # - Special users like root that may have special home directories # like /root instead of /home/root # - Local computer is not a Mac # - Remote sever is a Mac, not Linux/Unix # - Without such hardcoding, I haven't yet found a way to map local # Mac home directories of users other than the current user to # remote home directories of the same user. # - ?? Could examine the local /etc/passwd file to see if the # specified or default path is the home directory of any # user, but how to know what to map it to remotely? # - ?? Could add more options to this script, to allow pairs of # local/remote path prefix mappings, as: # --map /User/:/home/:/root:/home/root # Maybe someday... #?? Factor out this logic into a new strip_home_directory script? #?? It's currently used both here and in remotepub. set remotepath = "`echo '$remotepath' | normalize_filename | sed -e 's*^$HOME/*\./*' | sed -e 's*^$HOME"'$'"*\.*'`" endif # Default command is to run the default $SHELL at the remote server # after doing the CD command. When the user exits that shell, control # returns to the caller of this script. # Note: Need '' around '$SHELL' to avoid evaluating it locally, which # would cause the default remotely to be the local default shell, # not the remote default shell. set default_command = '$SHELL' if ($#argv > 0) set default_command = "" # Show in the window titlebar that we're currently ssh'd to the remote # server and the commands being executed there. # Note: Can afford to put both $* and $default_command here since we know # one of them is the empty string settitle "trident cd $remotepath && $*$default_command" # Execute the specified command in the specified folder at the remote server. # Note: Echo the ssh command for the user to see # Note: Also echo the cd command and specified command(s) for the user to see # Note: Use "&&" instead of ";" to force the whole ssh session to abort # if the current directory doesn't exist. # Note: Can afford to put both $* and $default_command here since we know # one of them is the empty string ## ## Doesn't work for: ## tridentcd -P sub1/sub2 "ls -FlA; rm -riv sub3; ls -FLA" ## Semicolon ends the echo of the command. The rest is executed, not echoed. ##(set echo; ssh -t $TRIDENT "echo % cd $remotepath && cd $remotepath && echo % $*:q$default_command:q && $*$default_command") ## ## Doesn't help. Need to wrap all args of echo, not wrap entire echo ## statement How to do so? Already using double quotes, don't want to ## use single quotes because it will suppress expansion on server of ## any ~ or variable passed to it. Parens don't group args, only ## statements. Maybe backticks? ##(set echo; ssh -t $TRIDENT "echo % cd $remotepath && cd $remotepath && (echo % $*:q$default_command:q) && $*$default_command") ## ## Doesn't help. Backticks are executed locally and split commands at ## semi-colons ##(set echo; ssh -t $TRIDENT "echo % cd $remotepath && cd $remotepath && echo % `echo $*:q$default_command:q` && $*$default_command") # # Best I've found so far. Doesn't echo contents of scripts run. Only # the top level commands themselves. # Echoes with aliases expanded. Is that a good thing or bad? Any way to # prevent it? # How to echo % in front of each command, like when I as doing it manually? (set echo; ssh -t $TRIDENT "echo % cd $remotepath && cd $remotepath && echo -n '% ' && set echo && $*$default_command") # Restore the titlebar after the SSH session settitle -h