#!/usr/local/bin/perl
# csh2sh - convert C-shell scripts to Bourne shell scripts
#
# $Id: csh2sh,v 1.68 1997/03/17 20:49:12 rca Exp $
#
# File:		csh2sh
# Author:	Bob Arnold, based in part on input from:
#		Olivier.Aubert@enst-bretagne.fr
#		Anthony Thyssen <anthony@cit.gu.edu.au>
#
# The base of this script was "csh2sh.pl" by Olivier.Aubert@enst-bretagne.fr.
# I started with his version 1.1, and Olivier was kind enough to send me
# 1.3 too.  I also incorporated comments on Olivier's script and a sed-based
# converter from Anthony Thyssen <anthony@cit.gu.edu.au>.  I am grateful to
# both of them for their initial work on this problem, especially since a
# web search and check of articles in comp.unix.shells and related newsgroups
# produced nothing that met my needs.  Their responses to my request for help
# in comp.unix.shells made it much easier to begin this task.  I would note
# that most of their original code has been modified by me, and any
# problems have probably been introduced by me.
#
# Another good source of information is "The UNIX(TM) C Shell Field Guide",
# by Gail Anderson and Paul Anderson.  This book has lots of valuable tips
# for csh users, including portability problems.  Its examples also use
# good coding style.
#
# Use this C-shell to Bourne shell script converter at *YOUR* own risk.
#
#		Bob
#
# Notes:
# 1) This script does not deal with interactive shell features
# like $history, $prompt, history (!) substitution or job control.
# So, it is not fully useful for turning .cshrc or .login into .profile.
# For that, you would want csh2ksh (C-shell to Korn-shell) anyway.
#
# 2) We assume that modern Bourne-shell features are available,
# including functions and the "unset" command.  As a result, the code
# generated by csh2sh may not conform to the Version 7 Bourne-shell.
# However, this will probably not be a problem for most systems these days.
# If your UNIX does not have these features, you may have to resort to
# evaluating MACRO's instead of calling functions, and turning "unset"
# variables into null strings instead of really unset'ing them.

# variable initialization
$RCSversion = q$Revision: 1.68 $;
($version) = ($RCSversion =~ /Revision:\s+([\d]+\.[\d]+)/);
$pr		= $0;		# name of this program
$pr		=~ s|.*\/||;	# basename of this program
chomp ($date	= `date`);	# today's date
$realuid	= (getpwuid($<))[0]; # my real uid
%exported	= ();		# already exported variables
%var		= ();		# build list of already defined variables here
$external_dir	= "/tmp";	# put converted source'd files here
$indent_s	= "    ";	# preferred indent string, in case we do our own
$sigs		= '1 2 3 15';	# signals for trap
$stamp		= "#$pr: converted by $realuid on $date\n"; # conversion date
$gethome	= "passwd | awk -F: '{print \$7}'\`";	# for expanding ~user

## regular expressions
# numerical tests look like a number, $status, $#argv, $$, $?0,
# `wc -X ...`, or `grep -c ...`; optionally preceded by a "!";
# and optionally quoted by doublequotes
$numval_re = '^(!\s+)?"?(\d+|\$status|\$\{status\}|\$#argv|\$\{#argv\}|\$\$|\$\?0|`.*(grep -c|wc -[lwc]).*`)"?$';
# string tests look like a null string,
# a possibly doublequoted string which begins with $0,
# a doublequoted string which doesn't begin with a number or variable,
# a singlequoted string which doesn't begin with a number, or
# something that looks `grep -l` (since most filenames are not pure numbers)
$strval_re = '^(\'\'|\"\"|\"?\$0|\"[^\d\$]|\'\D|\`.*grep\s+-l.*\`)';
$zero = '\s+\"?0\"?';
$one  = '\s+\"?1\"?';

# usage message
$usage		= "usage:
$pr -help
$pr [infile [outfile]]\n";

# lists of built-in commands and variables which we can't translate
@badcmd = ("bg","dirs","fg","glob","hashstat","history","jobs","kill\s+-l","limit","login","logout","nice","notify","popd","pushd","rehash","stop","suspend","unalias","unhash","unlimit","%");
@badvar = ("fignore","filec","hardpaths","histchars","history","ignoreeof","nobeep","noclobber","noglob","nonomatch","notify","prompt","savehist","time");

%transl_flag = (
    "b",  "",		# sh has decent command arg processing
    "c", "c",		# makes no sense in a script
    "e", "e",
    "f",  "",		# sh scripts donot read .profile
    "i", "i",		# makes no sense in a script
    "n", "n",
    "t", "t",
    "v", "v",
    "V", "v",		# as close as we can get
    "x", "x",
    "X", "x",		# as close as we can get
);

# translations for set-able csh variables
%trans_setvar = (
    "echo",	 "-x",
    "noglob",	 "-f",
    "verbose",	 "-v",
    "home",	 "HOME",
    "shell",	 "SHELL",
    "cdpath",	 "CDPATH",
);

# translations for unset-able csh variables
%trans_unsetvar = (
    "echo",	 "+x",
    "noglob",	 "+f",
    "verbose",	 "+v",
    "home",	 "HOME",
    "shell",	 "SHELL",
    "cdpath",	 "CDPATH",
);

# translations for "( ... )" test operators
%transl_tests = (
    "-r", "-r",
    "-w", "-w",
    "-x", "-x",
    "-e", "-f",
    "-o", "-w",	# Not exactly, but it should do the trick
    "-z", "! -s",
    "-f", "-f",
    "-d", "-d",
    "==", "-eq",	# could be string or numeric test
    "!=", "-ne",	# could be string or numeric test
    ">",  "-gt",
    ">=", "-ge",
    "<",  "-lt",
    "<=", "-le",
    "||", "-o",
    "&&", "-a",
);

# rewrite "test ! ..." to "test ..." where possible
%opposites = (
    "-n",  "-z",
    "-z",  "-n",
    "-ne", "-eq",
    "-eq", "-ne",
    "=",   "!=",
    "!=",  "=",
);

# check command args
if ($ARGV[0] =~ /^-h/) {
    print "$usage";
    exit 0;
}
$#ARGV > 1 && die "$usage";

# start our work - open files, etc.
$fichier	= shift(@ARGV) || '-';
$sortie		= shift(@ARGV) || '-';
open(FICHIER, $fichier)		|| die "$pr: Unable to open $fichier: $!\n";
open(SORTIE, "| fmt.script - $sortie")	|| die "$pr: Unable to create $sortie: $!\n";

#print STDERR "$pr: Converting $fichier\n";

# initialize main while loop values
$iflevel	= 0;		# track if...then/else if...then/else
$caselevel	= 0;		# track case
$dolevel	= 0;		# track for/while
$indent		= '';		# indent for each line
$temp		= '';		# temp line in case we get "\" at end of line
$prevcase	= 0;		# previous line was not a case pattern
$cpats		= '';		# to hold case patterns temporarily
$ccomin		= '';		# to hold inline case pattern comments
$ccomln		= '';		# to hold full line case pattern comments

# start getline loop from input file
GETLINE: while (<FICHIER>) {

    # Each line has to be processed, in *exactly* this order:
    # Step 1) Initial stuff
    # Step 2) first line: "#!/bin/csh" and related issues
    # Step 3) empty lines and '#' comments (handle comments in case patterns)
    # Step 4) join lines continued with backslash "\"
    # Step 5) strip trailing whitespace and get $indent
    # Step 6) handle switch/case patterns here
    # Step 7) handle rest of $line, put $indent in all returned lines, and print
    # That's it!  In *exactly* this order.

    # Step 1) Initial stuff
    local($line)	= $_;			# this line
    local($check)	= '';			# init $check for this line
    local($prob)	= '';			# init $prob for this line
    $line =~ s/[ \t]+$//;			# strip trailing whitespace

    # Step 2) handle first line
    if ($. == 1 ) {
	# if first line says "#whatever" we have a csh script
	if ($line =~ /^#($|[^!])/) {
	    print SORTIE "#!/bin/sh\n", $stamp, "$line\n";
	    next GETLINE;
	# else if first line says something like "#!/bin/csh" (with optional
	# flags), convert it to "#!/bin/sh" (with converted flags)
	} elsif ($line =~ m,^#!\s*\S*/bin/csh(\s+-([bcefinstvVxX]+))?,) {
	    @flags=split(//, $2);		# to split each char
	    foreach $flag (@flags) {
		if (defined($transl_flag{$flag})) {
		    $flags .= $transl_flag{$flag};
		}
	    }
	    $flags ne '' && ($flags = " -$flags");
	    print SORTIE "#!/bin/sh", $flags, "\n", $stamp;
	    next GETLINE;
	# else quit if we have definitely been handed a non-csh script;
	# we know we have been handed non-csh input if the first line begins
	# with a ":" or says something like "#!anything_else"
	} elsif ($line =~ /^(:|#!)/) {
	    print STDERR "$pr: $fichier is definitely not a csh script\n";
	    exit 1;
	# else we hope we have valid csh code for input, and warn that
	# the input is not an executable csh script
	} else {
	    print SORTIE "#!/bin/sh\n", $stamp;
	    print SORTIE "#NOT CSH SCRIPT: $line";
	}
    }

    # Step 3) do not modify empty lines or comments, but if the
    # previous line was a case pattern, this line *may* be a comment about
    # a following case pattern
    if ($line =~ /^\s*(\#|$)/) {
	# most comment lines won't be about a following case pattern
	if (! $prevcase) {
	    print SORTIE $line;
	# handle comment *possibly* about a following case pattern
	} else {
	    $ccomln .= $line;
	}
	next GETLINE;
    }

    # Step 4) watch out for backslash "\" at end of line
    # if this line is continued, append $temp buffer and get next line
    if ($line =~ /\\$/) {
	$temp .= $line;
	next GETLINE;
    # else if the previous line was continued by a backslash, finish $temp
    } elsif ($temp ne '') {
	$line  = $temp . $line;
	$temp  = '';
    }

    # Step 5) strip trailing whitespace get $indent
    chomp($line);				# strip newline
    $line =~ s/^(\s*)//;			# strip indentation
    $indent = $1;				# memorize indentation

    # Step 6) handle switch/case patterns here - this is hard because it is
    # the only csh keyword which has to be converted based on the *next* line,
    # which could be a full line comment, breaksw (in a donothing case),
    # continuation (\\n) line, or ordinary command.  Other problems include
    # many legal syntaxes for the word/pattern:
    #	inline comments on the pattern line
    #	lines which say only "case"
    #	lines with "case word", "case word:", "case word   ::  :::"
    #	case patterns without breaksw, which fall thru to the next case pattern
    #	(but the last case pattern can be terminated by "endsw"
    # Note that everything after the first "word" is ignored, but we want to
    # preserve in-line comments.  A really hard problem is that a pattern
    # is really a csh "word", which can include a quoted colon.  We try to
    # approximate this, but it would be better (and even harder) to do
    # real csh "word" analysis.
    #
    # We do not deal *at all* with the following problems:
    #	lines with "case word1 word2"
    #
    # This sh-style case statement expresses our strategy:
    # case $cpats.$line in
    # *.case)	continue;;			# trash dumb empty 'case' noop
    # ''.case*)	$cpats .=  "$1" ; continue;;	# this line is case
    # ?*.case*)	$cpats .= "|$1" ; continue;;	# prev and this lines are case
    # ?*.*)	print "$cpats)\n" ; $cpats = '';;	# prev was case
    # ''.*)	: do nothing ;;			# neither prev or this are case
    # esac
    # 
    # OK, let's implement it:
    $thiscase = 0;			# assume this line is not "case"
    $line =~ /^case$/ && next GETLINE;	# trash dumb empty 'case' noop
    # case pattern could look like any of these
    if ($line =~ /^case\s+([^:]*[^\s:]\s*:|('[^']+'|"[^"]+"|`[^`]+`|[^\s\#:]+)[\s:]*)((\s*)(#.*))?$/) {
	$thiscase++;			# this line has a case pattern
	($cpat = $1) =~ s/[\s:]+$//;	# case pattern is $1, strip space/colons
	$ccomin eq '' && ($ccomin = "$4$5") || ($ccomin .= " $5");
    }
    # if this is the first case pattern
    if      (! $prevcase &&   $thiscase) {
	$prevcase++;				# set $prevcase flag
	$cpats .= "$indent$cpat";		# start string of case patterns
	$npat[$caselevel]++;			# increment pattern counter
	if ($npat[$caselevel] > 1) {
	    print SORTIE "$indent# PREV CASE_PATTERN HAS NO BREAKSW!!\n";
	}
	next GETLINE;				# get next line
    # elsif this line is a case pattern too, seperate the patterns with a "|"
    } elsif (  $prevcase &&   $thiscase) {
	$cpats .= "|$cpat";			# add "|" and this case pattern
	$ccomln ne '' && print SORTIE $ccomln;	# print any full line comments
	$ccomln = "";				# init case full line comments
	next GETLINE;				# get next line
    # else we have retrieved all the case patterns, so print them out
    # along with any full line comments, and then keep processing this line
    } elsif (  $prevcase && ! $thiscase) {
	$prevcase--;				# clear $prevcase flag
	print SORTIE "$cpats)$ccomin\n";	# case patterns) inline comments
	$ccomln ne '' && print SORTIE $ccomln;	# print any full line comments
	$cpats = "";				# init case patterns
	$ccomin = "";				# init case inline comments
	$ccomln = "";				# init case full line comments
    }

    # Step 7) process the rest of the line
    # &do_line returns an array of strings, possibly containing newlines
    # &do_line also puts warnings and problems into $check and $prob
    # then we join all the strings together and insert $indent before all
    # returned lines
    #print SORTIE "\$.=$., indent='$indent', line='$line'\n";
    $shline = join("", &do_line($line));	# change $line(csh) to $shline
    $shline = &final($shline);			# global changes in $shline
    $*++;					# match "^" after all newlines
    $shline =~ s/^/$indent/g;			# indent all lines in $shline
    $*--;					# match "^" at start of string
    $check ne '' && print SORTIE "$indent#$pr:$check $line\n";	# show checks
    $prob ne '' && print SORTIE "$indent#$pr:$prob $line\n";	# show problems
    print SORTIE $shline;			# print final result
    $indent = '';				# start $indent over again
}
close(FICHIER);					# close input
$label && print SORTIE "}\n";			# close the last function if any
close(SORTIE);					# close output
$sortie ne '-' && system("chmod +x $sortie");	# make output executable

$iflevel	< 0 && warn "$pr: $fichier: missing if\n";
$iflevel	> 0 && warn "$pr: $fichier: missing endif\n";
$caselevel	< 0 && warn "$pr: $fichier: missing switch\n";
$caselevel	> 0 && warn "$pr: $fichier: missing endsw\n";
$dolevel	< 0 && warn "$pr: $fichier: missing for/while\n";
$dolevel	> 0 && warn "$pr: $fichier: missing end\n";
#;                      warn "$pr: $fichier converted.\n";

close(STDOUT);
close(STDERR);
exit;

# &do_line:
# * is most frequently called on an entire original line of input
# * assumes that any newlines at the end of line have already been stripped
# * assumes that the line indent has already been stripped, but it may
#   still get (and strip) leading whitespace from the input
# * is also called to handle commands like
#	(eval|exec|nohup|time) command
#	if ( ... ) command, or else if ( ... ) command
#	if { command1 } command2, or else if { command1 } command2
#	`command`
#	command1 || command2, or command1 && command2
#	command1 |  command2, or command1 |& command2
# * the general idea is that each calling routine handles the stuff it can,
#   and then calls &do_line on the remaining unprocessed stuff; &do_line
#   makes recursive &do_line calls when necessary
sub do_line {
    local($line)	= shift;		# get $line from @_
    local($eval)	= '';			# init $eval for this line
    local(@res)		= ();			# @res holds result strings

    #print SORTIE "\$.=$., indent='$indent', line='$line'\n";

    # handle built-ins which invoke other programs - eval, nohup, time, exec
    # (have to handle repeat seperately)
    if ($line =~ /^(((eval|exec|nohup|time)\s+)+)(.*)/) {
	return($1, &do_line($4));
    }

    ## handle all other lines, one case at a time
    # aliases - turn them into functions
    if ($line =~ /^alias\s+(\S+)?\s+(.+)$/) {
	$nom = $1;
	$alias = $2;

	# strip singlequotes and doublequotes
	$alias =~ s/^\'?(.+)?\'?$/$1/;
	$alias =~ s/^\"?(.+)?\"?$/$1/;

	# positional args on command line - turn "\!:[1-9*]" into "$[1-9*]";
	# the "($|\D)" part of the regex ensures we do it only for single digits
	if ($alias =~  /\\\!:?[1-9*]($|\D)/) {
	    $alias =~ s/\\\!\*($|\D)/\$*$1/g;		# \!* -> $*
	    $alias =~ s/\\\!:([1-9*])($|\D)/\$$1$2/g;	# \!:* -> $*
	}

	return($nom, "() { ", $alias, " }");
    # environment variables
    } elsif ($line =~ /^setenv\s+([A-Z_]+)\s+(.+)$/) {
	$nom = $1;
	$valeur = $2;
	$var{$nom} = $valeur;
	@res = ($1, "=", $2);
	if (!defined($exported{$1})) {
	    $exported{$1} = 1;
	    push(@res, "\nexport ", $1);
	}
	return(@res);
    # set commands are *really* hard
    } elsif ($line =~ /^set\s+/) {
	&do_set("$line");
    # unset/unsetenv commands are generally converted to 'unset'
    # (even though 'unset' is not in Version7 Bourne shell, since unset
    # exists in newer Bourne shells); the one exception is commands
    # like "unset verbose" which translates to "set +x"
    } elsif ($line =~ /^(unset|unsetenv)\s+(.+)/) {
	($unsetvars,$comment) = ($2 =~ /([^#]+[^\s#])(\s+#+[^#]*)?$/);
	@unsetvars = split(' ', $unsetvars);
	foreach $x (@unsetvars) {
	    if (defined($trans_unsetvar{$x})) {
		$unsetcmd .=   "set " . $trans_unsetvar{$x} . $comment . "\n";
	    } else {
	    	$unsetcmd .= "unset " . $x                  . $comment . "\n";
	    }
	}
	return($unsetcmd);
    # math commands with "@"
    } elsif ($line =~ /^@/) {
	&do_math("$line");
    # lines which evaluate exit status "{ command }", not normal tests "( ... )"
    # convert "while { command1 }" to "while command1 ; do"
    } elsif ($line =~ /^while\s+\{\s+(.*\S)\s+\}\s*$/) {
	$dolevel++;
	return("while ", &do_line($1), " ; do");
    # convert "if { command1 } then" to "if command1 ; then"
    } elsif ($line =~ /^if\s+\{\s+(.*\S)\s+\}\s+then\s*$/) {
	$iflevel++;
	return("if ", &do_line($1), " ; then");
    # convert "else if { command1 } then" to "elif command1 ; then"
    } elsif ($line =~ /^else\s+if\s+\{\s+(.*\S)\s+\}\s+then\s*$/) {
	$iflevel=$iflevel;
	return("elif ", &do_line($1), " ; then");
    # else if we have an "(else )?if { command1 } command2" situation, where
    # command1 and/or command2 could have curlies too - this is hard
    # tricky stuff to try find which close curlie we strip
    } elsif ($line =~ /^(if|else\s+if)(\s+)(\{\s+)(.*)/) {
	# using parens inside the split pattern yields the matching pattern too
	@curlfrag = split(/(\s+[\{\}]\s+)/, $line);	# split at curlies
	$nocurl   = split(/\s+[\{]\s+/,     $line);	# count open curlies
	$nccurl   = split(/\s+[\}]\s+/,     $line);	# count close curlies
	$unclosed = 0;			# number of unmatched open curlies
	for ($i=1; $i<$#curlfrag; $i+=2) {
	    $curlfrag[$i] =~ /\s+[\{]\s+/ ? $unclosed++ : $unclosed--;
	    $unclosed || last;	# break if we have found our match
	}
	if ($unclosed) {
	    $prob .= " NO CURLIE MATCH:";
	} else {
	    # convert "if { command1 } command2" to "command1 && command2"
	    if ($curlfrag[0] =~ /^if$/) {
		$iflevel=$iflevel;
		$curlfrag[0] = "";
		$curlfrag[1] = "";
		$curlfrag[$i] = " && ";
		return(@curlfrag);
	    # convert "else if { command1 } command2"
	    # to "elif command1 ; then\n${indent_s}command2\nfi"
	    } else {
		$iflevel--;
		$curlfrag[0] = "elif ";
		$curlfrag[1] = "";
		$curlfrag[$i] = " ; then\n";
		@res = (@curlfrag[0..$i]);
		$cmd2 = join('', @curlfrag[$i+1..$#curlfrag]);
		push(@res, "$indent_s$cmd2\n");
		push(@res, "fi");
		return(@res);
	    }
	}
    # deal with more common test conversions for "if/else if/while ( ... )"
    } elsif ($line =~ /^while\b/) {
	&do_ifwhile($line);
    } elsif ($line =~ /^if\b/) {
	&do_ifwhile($line);
    } elsif ($line =~ /^else\s+if\b/) {
	&do_elsif($line);
    } elsif ($line =~ /^else\b/) {
	$iflevel == 0 && &unable($line);
	return("else");
    } elsif ($line =~ s/^endif\b/fi/) {
	$iflevel == 0 && return;	# skip stupid unmatched endif csh noop
	$iflevel--;
	return("$line");
    } elsif ($line =~ /^foreach\s+(\w+)\s*\(\s*(.+\S)?\s*\)/) {
	$var  = $1;
	$list = $2;
	if ($list eq '') {
	    $prob .= " EMPTY FOREACH LIST:";
	}
	$dolevel++;
	return("for $var in $list ; do");
    } elsif ($line =~ s/^end\b/done/) {
	$dolevel == 0 && &unable($line);
	$dolevel--;
	return("$line");
    } elsif ($line =~ /^(break|continue)\b/) {
	&do_loopcntl($1,$line);
    } elsif ($line =~ /^switch\s*\(\s*(.+\S)?\s*\)/) {
	$valeur = $1;
	if ($valeur eq '') {
	    $prob .= " EMPTY SWITCH:";
	}
	$caselevel++;
	$npat[$caselevel] = 0;		# init npat counter for this switch
	return("case $valeur in");
    } elsif ($line =~ /^breaksw\b/) {
	$caselevel == 0 && &unable($line);
	$npat[$caselevel]--;
	return(";;");
    } elsif ($line =~ /^default\s*:?\s*$/) {
	$caselevel == 0 && &unable($line);
	return("*)");
    } elsif ($line =~ /^endsw\b/) {
	$caselevel == 0 && &unable($line);
	$caselevel--;
	$npat[$caselevel] = 0;		# end npat counter for this switch
	return("esac");
    # repeat command - 
    } elsif ($line =~ /^repeat\s+(\d+)\s+(.*)/) {
	@res = ("oldn=\$n");		# in case the csh script uses $n already
	push(@res, "n=0");
	push(@res, "while test \$n -lt $1 ; do");
	push(@res, "${indent_s}n=\`expr \$n + 1\`");
	push(@res, "${indent_s}$2");
	push(@res, "done");
	push(@res, "n=\$oldn");
	return(@res);
    # convert "goto" to a function call
    } elsif ($line =~ /^goto\s+(.*)/) {
	$check .= " GOTO->FN:";
	return($1);
    # handle various "onintr" meanings
    } elsif ($line =~ /^onintr\b/) {
	if      ($line =~ /^onintr\s+-/) {
	    $line =~ s/^onintr\s+-/trap '' $sigs/;
	} elsif ($line =~ /^onintr\s+(\w+)/) {
	    $prob .= " ONINTR->FN:";
	    $line =~ s/^onintr\s+(\w+)/trap "$1" $sigs/;
	} else {
	    $line =~ s/^onintr\s+/trap $sigs/;
	}
	return($line);
    # handle labels:
    # If we have already seen a label/function, that one may or may not
    # be falling through to this one; we have no easy way to know :-(
    # Since we can't tell, take the easy choice, namely to just close the
    # previous label/function, and then begin this one.
    # Hack: print closing curlie brace directly to SORTIE so it appears before
    # $prob; also we assume there is no indent, which is a fairly safe bet.
    } elsif ($line =~ /^(\w+):\s*$/) {
	print STDERR "$pr: NOTE: label converted to function (line $.): '$line'\n";
	$prob .= " LABEL->FN:";			# warn user
	$label ne '' && print SORTIE "}\n";	# hack print closing curlie
	$label = $1;				# save $label
	return("$1() {");			# return beginning of function
    # if we source an external file, convert the file to sh code
    # external_prog puts the converted copy in $external_dir
    } elsif ($line =~ /^source\b/) {
	$check = "$check SOURCE FILE:";
	$file = &external_prog($line);
	if ($file == -1) {
	    &unable($line);
	} else {
	    return(". $file");
	}
    } else {
	return($line);
    }
}

sub do_set {
    local($line) = shift;
    local(@cmdsub);
    local(@val);
    local(@valvar);
    local(@vars);
    local(@res);

    # This is valid "set" code, which makes this hard work:
    # "set var1 var2 var3=val3 var4 var5 = val5 var6 = ( word list ) var7 ="
    #
    # Our strategy is to split off any comments, parse the
    # set commands, examine the results for $path and parenthesized
    # word lists, and then print each set command on its own line
    # together with the original comment.
    #
    # Note that only a variable can come *before* an equalsign (=),
    # whereas a value and possibly multiple variables can come after.
    # This means that it is easier to split the setcode on equalsigns
    # and then nibble variable names *backwards* from the resulting
    # value_variable(s) strings.
    #
    # It also helps that quoting the variable *name* is a csh syntax error,
    # so \w+ is a sufficient description of a valid variable name.
    #
    # Each time we nibble variable names from a value_var(s) string, the
    # first nibble (variable name) is assigned a value from the *previous*
    # value_var(s) string.  As we figure out variable=value assignments,
    # they are put in a @vars array; we use "unshift" to put them there
    # because we are working backwards.
    #
    # We also have @cmdsub to deal with command substitutions (backquotes) like:
    #	set foo=`awk '$1 == host' host=$host`
    ($setcode, $comment) = ($line =~ /([^#]+[^\s#])(\s+#+[^#]*)?$/);
    while ($setcode =~ /(`[^`]+`)/) {
	$setcode =~ s/(`[^`]+`)/COMMANDSUB/;
	push(@cmdsub,$1);
    }
    @valvar = split(/\s+=\s+|\s+=$|=$|=/, $setcode);
    $prevval = '';
    foreach $valvar ( reverse @valvar ) {
	# remember what we did the *previous* time through this foreach loop
	#print STDERR "valvar='$valvar'\n";
	# while we can nibble $variable names
	while ( ($val, $variable) = ($valvar =~ /(.*)\s+(\w+)$/) ) {
	    if ( $prevval eq "" ) {
		$thisval = "''";
	    } else {
		$thisval = $prevval;
		$prevval = '';
	    }
	    unshift(@vars, "$variable=$thisval");	# save this var=val pair
	    $valvar = $val;			# prep for nibbling another var
	}
	$prevval = $valvar;			# $prevval gets the leftovers
	#print "in for, after while: valvar='$valvar'\n";
    }
    #print "last valvar='$valvar'\n";
    # now that we have val=var pairs, handle each one
    for $var ( @vars ) {
	#print STDERR "var='$var'\n";
	($nom, $valeur) = split(/=/, $var);
	if ($valeur =~ /COMMANDSUB/) {
	    $cmdsub = shift @cmdsub;
	    $valeur =~ s/COMMANDSUB/$cmdsub/;
	}
	$var{$nom} = $valeur;

	# warn if we see variables we know we can't translate
	if (grep(/^$nom$/, @badvar)) {
	    $prob .= " BADVAR:$nom:"
	}

	# if $var is easily translated
	if (defined($trans_setvar{$nom})) {
	    #print STDERR "var='$var' trans='$trans_setvar{$nom}'\n";
	    push(@res, "set $trans_setvar{$nom}$comment", "\n");
	# deal with "set argv=whatever"
	} elsif ($nom eq "argv") {
	    push(@res, "set ", $valeur, $comment, "\n");
	# deal with the ugly csh correspondance between path and PATH
	} elsif ($nom eq "path") {
	    $valeur =~ s/^\(\s*//;	# strip open paren and whitespace
	    $valeur =~ s/\s*\)$//;	# strip close paren and whitespace
	    $valeur =~ s/\s+/:/g;	# convert whitespace to colons
	    $valeur =~ s/(\$path|\$\{path\})(:|$)/\$PATH$2/;
	    push(@res, "PATH=", $valeur, $comment, "\n");
	    if (!defined($exported{"PATH"})) {
		$exported{"PATH"} = 1;
		push(@res, "export PATH$comment", "\n");
	    }
	# deal with "set mail=([#] /usr/spool/mail/$user)" (number is optional)
	} elsif ($nom eq "mail") {
	    $valeur =~ s/^\(\s*(.*\S)\s*\)$/$1/;
	    @val = split(/ /, $valeur);
	    if ($val[0] =~ /^\d+$/) {
		push(@res, "MAILCHECK=$val[0]$comment", "\n");
		shift @val;
	    }
	    if ($#val == 0) {
		push(@res, "MAIL=$val[0]$comment", "\n");
	    } else {
		push(@res, "MAILPATH=", join(':', @val), "$comment", "\n");
	    }
	# else if it is a parenthesized word list, try to quote it
	# which is the best we can do
	} elsif ($valeur =~ /^\(\s*(.*\S)\s*\)$/) {
	    $valeur = $1;
	    # if $value has no doublequotes, then doublequote it
	    if ($valeur !~ /\"/) {
		$valeur = '"' . $valeur . '"';
	    # else if $value has no singlequotes or dollars, singlequote it
	    } elsif ($valeur !~ /[\"\$]/) {
		$valeur = "'" . $valeur . "'";
	    # else do leave it alone
	    } else {
		$valeur = $valeur;
		$prob .= " PAREN_LIST:";
	    }
	    push(@res, $nom, "=", $valeur, $comment, "\n");
	} elsif ($valeur =~ /^\$<|\`(gets|line)\`$/) {
	    push(@res, "read ", $nom, $comment, "\n");
	} else {
	    push(@res, $nom, "=", $valeur, $comment, "\n");
	}
    }
    pop(@res);		# pop final newline off the @res stack
    return(@res);	# return result array
}

sub do_ifwhile {
    local($line) = shift;
    if ($line =~ /if\s*\((.+)?\)\s*then\b/) {
	$test = $1;
	$iflevel++;
	return("if ", &convert_test($test), " ; then");
    } elsif ($line =~ /if\s*\((.+)?\)\s*(.+)/) {
	$test = $1;
	$iflevel=$iflevel;
	return("", &convert_test($test), " && $2");
    } elsif ($line =~ /while\s*\((.+)?\)/) {
	$test = $1;
	$dolevel++;
	return("while ", &convert_test($test), " ; do");
    }
}

sub do_elsif {
    local($line) = shift;
    local(@res)	= ();
    if ($line =~ /else\s+if\s*\((.+)?\)\s+then/) {
	$test = $1;
	return("elif ", &convert_test($test), " ; then");
    } elsif ($line =~ /else\s+if\s*\((.+)?\)\s+(.+)/) {
	$test = $1;
	$iflevel--;
	#print STDERR "got to elif (...) command\n";
	push(@res, "elif ", &convert_test($test), " ; then");
	push(@res, $indent_s, &do_line($2));
	push(@res, "fi");
	return(@res);
    }
}

# handle "break" and "continue" loop control statements
# csh executes any commands on the current line following a "break", to
# allow multi-level breaks to be written as "break ; break"
# csh also executes any commands on the current line following a "continue"
# but you can't do multi-level continues this way :-(
# in csh, if you write "continue ; continue", csh skips both the
# current and next loop iterations, and then executes the iteration after that
# while this might be useful there is no Bourne shell equivalent.
# if you write "continue ; whatever", then "whatever" is executed before the
# continue happens
sub do_loopcntl {
    local($cntl) = shift;
    local($line) = shift;
    local($code, $comment, $followup, @followup, @words, $nword);
    ($code, $comment) = ($line =~ /([^#]+[^\s#])(\s+#+[^#]*)?$/);
    #print STDERR "\$code='$code'\n";
    #print STDERR "\$comment='$comment'\n";
    # if we have a simple control statement
    if ($code =~ /^$cntl[;\s]*$/) {
    	return($line);
    # if we have followup code to a continue statement
    } elsif ($code =~ /^continue\s*;\s*(\S.*)/) {
	$prob .= " '$cntl' FOLLOWUP:";
	return(&do_line($1), $comment, "\n", "continue", $comment);
    # if we have something like "break ; break" or "break ; whatever"
    } elsif ($code =~ /^(break(\s*;\s*break)+)\s*(\S.*)?$/) {
	# if we got "break ; whatever", handle the "whatever" part
	if ($3 ne '') {
	    $prob .= " '$cntl' FOLLOWUP:";
	    $followup = $3;
	    $followup =~ s/^\s*;\s*//;
	    @followup = (&do_line($followup), $comment, "\n");
	}
	@words = split(/\b(break)\b/, $1);	# split, including "break" words
	$nword = grep(/^break$/, @words);	# count "break" words in @words
	if ($nword == 1) {
	    return(@followup, "break$comment");
	} else {
	    return(@followup, "break $nword$comment");
	}
    }
}

# handle "@ whatever" math commands
sub do_math {
    local($line) = shift;
    if ($line =~ /^@\s+$/) {
	return("set");
    }
    $line =~ s/^@\s+//;
    # if we're like @ var--
    if      ($line =~ /(\w+)\s*([-+]){2}/) {
	$nom = $1;
	$args = "\$$nom $2 1";
    # elsif we're like @ var *= stuff
    } elsif ($line =~ /(\w+)\s*([-+*\/%])=\s*(.*)/) {
	$nom = $1;
	$args = "\$$nom \\$2 ( $3 )";
    # elsif we're like @ var = stuff
    } elsif ($line =~ /(\w+)\s*=\s*(.*)/) {
	$nom = $1;
	$args = "$2";
    # else we don't know how to handle this
    } else {
	$prob .= " AT/EXPR:";
	return;
    }
    $args =~ s/`/\\`/g;			# quote backticks: @ nchar=`wc -c $file`
    $args =~ s/([*()])/ \\$1 /g;	# quote and space asterisks and parens
    $args =~ s/\s\s+/ /g;		# strip extra whitespace
    return("$nom=\`expr $args\`");	# finish
}

# convert if/while ( ... ) tests
# The strategy is this:
# 1) strip leading/trailing whitespace
# 2) split on logicals &&/|| into @test components, saving the logicals
#	the test *elements* will have even indeces in @test ($test[0,2,4,...])
#	the test *logicals* will have odd  indeces in @test ($test[1,3,5,...])
# 3) loop through the test elements (even indeces), converting as we go
#	usually we convert "if/while ( .... )" to "if/while test ..."
#	but sometimes converting elements into "true" or "[ef]?grep"
# 4) loop through the test elements again (even indeces), joining them
#    with the appropriate logicals:
#	usually we join elements with "-a/-o" for "test" commands,
#	but sometimes leaving them as "&&/||" for "true/[ef]?grep"
# 5) join them all up appropriately at the end of this subroutine
sub convert_test {
    local($test) = shift;	# get $test from @_
    local($res);		# result of test element conversion
    local(@test) = ();		# array of unconverted test components
    local(@tres) = ();		# array of converted test elements
    local(@tcmd) = ();		# commands array: test, true, grep/egrep/fgrep

    # item 1
    $test =~ s/^\s+//;		# strip leading white space
    $test =~ s/\s+$//;		# strip trailing white space
    @test = split(/\s*(&&|\|\|)\s*/, $test);	# get test components

    # 3) convert individual test elements
    #for $t (@test) { print STDERR "\$test='$t'\n"; }
    for ($i=0; $i<=$#test; $i+=2) {
	#print SORTIE "i=$i, test_i=$test[$i]\n";
	$res = '';			# result of converted test element
	$t = @test[$i];			# test element we are converting
	$tcmd[$i] = 'test';		# most common, but may be reset for grep
	# if we got something like "while ( 1 )"
	if ($#test == 0 && $t =~ /^(1|"1"|'1')$/) {
	    $tcmd[$i] = 'true';
	    $res      = 'true';
	# if this is the *first or last* test, and it looks like a `grep -c`,
	# and it (implicitly or explicitly) compares the 'grep -c' output to 0,
	# then we have a simple grep/egrep/fgrep -c test which can be converted
	# into a real "grep/egrep/fgrep" command instead of a "test" command.
	} elsif ( ($test[0] == $t || $test[$#test] == $t) && $t =~ /\"?`\s*([ef]?grep)\s+-c.*`\"?\s*($|!=$zero|>$zero|>=$one)/ ) {
	    $tcmd[$i] = $1;		# "*grep" is this command
	    $t =~ s/^[^`]*`//;		# strip start thru first backtick
	    $t =~ s/`[^`]*//;		# strip last backtick thru end
	    $t .= ' > /dev/null';	# make sure we don't see *grep output
	    $t =~ s/grep\s+-c/grep/;	# strip the " -c"
	    $res = $t;
	# else if this test looks like an implied numerical test,
	# convert it to an explicit numerical test by appending " -ne 0"
	# (this works even for $status)
	} elsif ($t =~ /$numval_re/) {
	    #print SORTIE "implied numerical\n";
	    $res = "$t -ne 0";
	# else if this looks like an implied numerical test to see
	# whether a variable has been set, convert it to a string test
	} elsif ($t =~ /^(!\s+)?"?\$\?(\w+)"?$/ || $t =~ /^(!\s+)?"?\$\{\?(\w+)\}"?$/) {
	    #print SORTIE "varset check\n";
	    $nom = $2;
	    $res = "$1" . '"${' . $nom . '-UNSET}" != UNSET';
	# else if it says something like '( $foo )' then $foo *has* to
	# be a number or the empty string or else C-shell will barf;
	# let's assume that the C-shell script handles this properly;
	# in csh, '' and 0 both evaluate false, and positive integers are true
	# csh: '( $nom )' converts to sh: 'test \( -n "$nom" -a "$nom" -gt 0 \)'
	# so code it that way
	} elsif ($t =~ /^(!\s+)?"?\$(\w+|\{(\w+)\})"?$/) {
	    #print SORTIE "simple number\n";
	    $nom = $2;
	    $res = "$1\\( -n \"\$$nom\" -a \"\$$nom\" -gt 0 \\)";
	# else if it is testing against the null string, convert to "-z" or "-n"
	} elsif ($t =~ /^(!\s+)?("?\$(\w+|\{\w+\})"?)\s+(==|!=)\s+(\'\'|\"\")$/) {
	    #print SORTIE "null string\n";
	    if ($4 eq '==') {
		$res = "$1-z $2";	# found test against zero-length string
	    } else {
		$res = "$1-n $2";	# found test against non-empty string
	    }
	# else if it is doing a glob pattern match, try sneaky case statement
	} elsif ( $t =~ /^(!\s+)?("?\$(\w+|\{\w+\})"?)\s+(=~|!~)\s+(.*)$/ ) {
	    if      ( "$1$4" eq  '=~' || "$1$4" eq '!!~' ) {
		$res = "\`case $2 in $5) echo match ;; *) echo nomatch ;; esac\` = match";
	    } elsif ( "$1$4" eq  '!~' || "$1$4" eq '!=~' ) {
		$res = "\`case $2 in $5) echo match ;; *) echo nomatch ;; esac\` = nomatch";
	    }
	# else handle normal test elements
	} else {
	    #print SORTIE "else test leftover\n";
	    @token = split(/\s+/, $t);
	    #for $i ( @token ) { print STDERR "\$token='$i'\n"; }
	    $argn=-1;
	    for $j ( @token ) {
		$argn++;
		if ( defined($transl_tests{$j}) ) {
		    # if we can do simple test translation, do it
		    if ( $j ne "==" && $j ne "!=" ) {
			$res .= " $transl_tests{$j}";
		    # else the test is "==" or "!=" which can be
		    # unclear whether they are string or numeric tests
		    # so try to figure it out
		    } else {
			# comparing to a definite string implies a string test
			if ( $token[$#token] =~ /$strval_re/ ) {
			    $j eq "==" && ( $res .= " =" );
			    $j eq "!=" && ( $res .= " !=" );
			# comparing to a number implies a numeric test
			} elsif ( $token[$#token] =~ /$numval_re/) {
			    $j eq "==" && ( $res .= " -eq" );
			    $j eq "!=" && ( $res .= " -ne" );
			# else guess string test (and note prob) since "the
			# result of all expressions are strings, which may
			# represent decimal numbers" (SunOS csh(1) man page)
			} else {
			    $j eq "==" && ( $res .= " =" );
			    $j eq "!=" && ( $res .= " !=" );
			    $prob .= " TEST EQUALS NUM/STRING? '$t':";
			}
		    }
		# else if we are trying to see if $?var is set or not
		} elsif ( $j =~ /^\$\?(\w+)$/ || $j =~ /^\$\{\?(\w+)\}$/ ) {
		    $nom = $1;
		    # if $?nom is the last token, create a string test for it
		    if ( $argn == $#token || $test[$argn+1] =~ /(&&|\|\|)/ ) {
			$res .= ' "${' . $nom . '-UNSET}" != UNSET';
		    # else convert it to a numeric value, and hope the
		    # following args interpret it that way
		    } else {
			$res .= ' `test "${' . $nom . '-UNSET}" != UNSET && echo 0 || echo 1`';
		    }
		} else {
		    $res .= " $j";
		}
		$res =~ s/^ //;		# strip leading space
	    }
	}
	$tres[$i] = $res;
    }

    # step 4) convert logicals from "&&/||" to "-a/-o" as necessary
    # note if the test was something like "grep this || grep that" then
    # no logical and/or conversions will be necessary
    for ( $i=0; $i<$#test; $i+=2 ) {
	# if $tcmd for this test element and the next one is 'test', then
	# convert the test and/or into "-a/-o", otherwise leave it as "&&/||"
	$tres[$i+1] = $test[$i+1];
	if ( $tcmd[$i] eq 'test' && $tcmd[$i+2] eq 'test' ) {
	    $tres[$i+1] = $transl_tests{$tres[$i+1]};
	}
    }

    # step 5) build $return string
    # note we have to put the word 'test ' before any "test" command arguments
    for ( $i=0; $i<=$#test; $i+=2 ) {
	if ( $tcmd[$i] eq 'test' ) {
	    if ( $i == 0 || $tcmd[$i-2] ne 'test' ) {
		$tres[$i] = "test " . $tres[$i];
	    }
	    # make sure any parens are backquoted
	    $tres[$i] =~ s/([^\\])([\(\)])/$1\\$2/g;
	}
    }
    # return test elements and logicals, joined by spaces
    return join(" ", @tres);
}

# &final - do final global processing for each set of input lines
sub final {
    local(@lines)	= @_;
    local($line)	= join('', @lines);

    # some warnings before substitutions
    $line =~ /awk\s.*'.*~.*'/	&& ($prob .= " TILDA IN AWK CODE:");
    $line =~ /\$\?0/		&& ($prob .= " INPUT KNOWN:");
    $line =~ /^\$\{?child\}?/	&& ($prob .= " '\$CHILD'?='\$!':");
    foreach $cmd ( @badcmd ) {
	if ( $line =~ /^($cmd)\b/ ) {
	    ($x = $1) =~ tr/a-z/A-Z/;
	    $prob .= " $x:";
	    last;
	}
    }

    # substitutions here, before we print it	  C-shell	Bourne-shell
    $line =~ s/\$#argv|\$\{#argv\}/\$#/g;	# $#argv	$#
    $line =~ s/\$\{?argv\[\*\]\}?/\$*/g;	# $argv[*]	$*
    $line =~ s/\$\{?argv\}?([^\[]|$)/\$*$1/g;	# $argv		$*
    $line =~ s/\!:?\*/\$*/g;			# !*		$*
    $line =~ s/\$\{?argv\[(\d+)\]\}?/\$$1/g;	# $argv[N]	$N
    $line =~ s/\\!/!/g;				# \!		!
    $line =~ s/\$status\b|\$\{status\}/\$?/g;	# $status	$?
    $line =~ s/\$child\b|\$\{child\}/\$!/g;	# $child	$!
    $line =~ s/\$cwd\b|\$\{cwd\}/\`pwd\`/g;	# $cwd		`pwd`
    $line =~ s/(\s+)~([\W]|$)/$1\$HOME$2/g;     # ~		$HOME
    $line =~ s/~(\w+)/\`ypmatch $1 $gethome/g;	# ~user		sh equivalent
    $line =~ s/>\&\s*(\S+)/> $1 2>\&1/g;	# >& arg	> arg 2>&1
    $line =~ s/\|\&/2>\&1 |/g;			# |&		2>&1 |
    $line =~ s/^exit\s*\(?([^()]+)\)?/exit $1/;	# exit($stat)	exit $stat
    $line =~ s/^nohup\s*(#.*)?$/trap '' $sigs/;	# nohup		trap '' sigs
    $line =~ s/^chdir\b/cd/;			# chdir		cd
    # variable substitution modifiers are :e :h :q :r :t :x; they mean:
    # keep .suffix, keep head, quote var, remove .suffix, keep tail, quote words
    # do easy ones only :e :h :r :t
    $line =~ s|(\$\{?\w+):e(\}?)|\`expr $1$2 : '.*\\.\\\([^.]*\\\)\$'\`|g;
    $line =~ s|(\$\{?\w+):h(\}?)|\`echo $1$2 \| sed -e 's\|\/[^/]*\$\|\|'\`|g;
    $line =~ s|(\$\{?\w+):r(\}?)|\`echo $1$2 \| sed -e 's\|\.[^.]*\$\|\|'\`|g;
    $line =~ s|(\$\{?\w+):t(\}?)|\`basename $1$2\`|g;
    # conversions to put back stuff we mangled for safety
    $line =~ s/EQUALEQUAL/==/g;			# for set code

    $line =~ /\$\{?\w+\[/	&& ($prob .= " ARRAY SUBSCRIPT:");
    $line =~ /\$([1-9]\d+)/	&& ($prob .= " \$$1>\$9:");
    $line =~ /\$\{?\w+:[qx]\}?/	&& ($prob .= " QUOTED_VAR:");
    $line =~ /\{\S*,\S*\}/	&& ($prob .= " CURLIE GLOB?:");
    $line =~ /~[\w\$]+/		&& ($prob .= " TILDA$VAR:");

    # push it onto the output buffer
    return($line . "\n");
}

# convert scripts sourced by the script we are processing
sub external_prog {
    local($line) = shift;
    local($orig);
    local($basename);

    ($orig) = ($line =~ m|^\s*source\s+(\S+)|);

    # $orig =~ s|\$(\w[\w\d]*)|$var{$1}|g;
    # if the source file is specified by a variable name, try to find it
    while ($orig =~ m|\$([_a-zA-Z][\w]*)|g) {
	$nom = $1;
	if (defined $var{$nom}) {
	    $orig =~ s|\$$nom|$var{$nom}|;
	} else {
	    $prob .= " NO SOURCE FILE FOUND:";
	    return -1;
	}
    }

    $basename = $orig;
    $basename =~ s|.*/||;
    print STDERR "$pr: $fichier: Converting $orig in 'csh' to $basename in 'sh'.\n";
    system($0, $orig, "$external_dir/$basename");

    return("$external_dir/$basename");
}

sub unable {
    local($s) = shift;
    warn "$pr: $fichier: Unable to convert line $.: $s\n";
}

# first pass we have dealt with:
# a few robustness and style issues
# adding $pr to program messages
# a crack at converting $?nom to a numeric test
# a crack at converting $?nom as appropriate
# bug fix in "if ( whatever ) command" conversion
# general understanding of the script
#
# second pass
# while loops
# why do tests have so many spaces in them
# formatting if/while test statements "keyword<nospace>" [<space>token ...]
# translate each expression subtest, delimited by || && and ( )
# tests which use implicit numeric tests - this is hard
# tests which use implicit $status tests - also hard
# tests which use "!" as in "! $status"
# tests which use 'grep -c' and 'wc -X' are probably implicit numeric tests
#
# third pass
# more things that look like numbers: $$, $#argv, ${status}, ${#argv}, $?0
#
# fourth pass
# if/while statements that use exit status {} instead of numbers ()
# convert $status to $?
# convert $#argv to $#
# convert argv[\d] to $\d (warn if \d > 9)
# first look at sed script - done stuff marked with "done:"
#
# fifth pass
# finish looking at sed script - done stuff marked with "done:"
# convert ~ to $HOME
# bug fixes
#
# sixth pass
# set command conversion
# fix indentation
# better test conversion
# convert Olivier's perl coding style to my perl coding style
# warnings for things we can't translate, like
#	$argv[10 or more]
#
# seventh pass
# better test/grep and test vs. null string conversion
# put ":" between all PATH dirs
#
# eighth pass
# indente buffers output, main loop prints "$prob: $_" and buffer for each $_
# convert "while ( 1 )" to "while true ; do"
# convert "exit (#)" to "exit #"
# convert "$argv" to "$*"
# convert "onintr -" to "trap '' 1 2 3 15"
# look for $var:t and ${var:h} commands to turn into basename/expr commands
# @ expressions convert to expr, $var++/-- *= += -= /=
# warnings for things we can't translate, like
# 	untranslatable built-in commands
#	subscripted arrays
#	unlimit
#	:q and :x commands imbedded in variables
#	builtin commands
#	$?0
#	glob {} ?
#
# ninth pass
# make break/continue work
#
# ==================================================
# tenth pass based on Olivier's csh2sh.pl.1.3
# misc bug fixes
# deal with "\" at the end of line
# deal with eval
# ==================================================
#
# eleventh pass
# disclaimer saying we don't worry about history substitution and other
# interactive features like $prompt, $history, job control, etc
# deal with "else command" $iflevel-- vs. "else\ncommand" $iflevel=$iflevel
# convert "goto label" to function which exits
# convert "onintr label" to "trap 'function' 1 2 3 15"
# convert labels to functions - function is label to next label or EOF
# $0 is 99.999% guaranteed to be a string
# warn about vars we know we can't translate
# handle exec'd flags
# warn about multiple cases, which is the best we can do :-(
# convert ${var} to $var whenever possible
# convert ~user to `ypmatch user passwd | awk -F: '{print $7}'`
#
# twelfth pass
# handle mult-line case label: - this was HARD
# final csh builtin command survey
# bug fixes for external_prog
# usage is now "$pr -help\n$pr [infile [outfile]]\n"
# make sure we exec perl5
# print line number of file we are converting
# make sure output file is executable
# handle command substitions in set commands
# use sed instead of expr to deal with :h
#
# thirteenth pass
# improved set conversion
# handle more variables $child, $noglob
# handle empty nohup
# handle set var=`(gets|line)`
# handle tests against `grep -l` which is probably a string
# handle =~ and !~ tests - convert to case
#
# fourteenth pass
# convert entirely to perl4
# better handling of continued lines
# strip trailing whitespace
#
# fifteenth pass
# better first line handling
# better backslash continuation handling
# better comment handling
# better switch/case conversion
# warn if previous case pattern has no breaksw
#
# sixteenth pass
# have &do_line return an array of strings
# change "&indente(...)" to "return(...);"
#
# seventeenth pass
# big code cleanup
# fix problems with "return(...)"
# eliminate $buf
# eliminate postfix if statements (bad style)
# change $#var=-1; to @var=();
#
# eighteenth pass
# handle case where input is definitely not a csh script
#
# nineteenth pass -	MAJOR MILESTONE HURRAH!!
#			used to convert all tivoli-related
#			csh scripts to sh!!!
# put $stamp into output
# fix several bugs
# move a couple of inline subs from csh2sh to fmt.script
#
# twentieth pass
# handle parentheses in &convert_test
# handle logical and/or in &convert_test, and cleaner handling of grep -c
#
# twentyfirst pass
# handle unset better
# fix onintr handling bug
# fix break/continue handling bugs
# fix exit handling bug
#
# twentysecond pass
# pipe output into fmt.script for formatting cleanup
#
# need:
# parsing commands - we need to handle "if ( ... ) set ..." and other csh commands
# run &do_line on backquoted string
# sounds like we have to create recursion "do_cmd" :-(
# almost any single word, or !word, is an implied numerical test
# &do_line should handle all eval's internally, not call &do_line
# strip $comment in main while loop
# append  $comment to each returned line if /^(set|unset|???)/ else $shline .= $comment
# prepend $eval    to each returned line if /^(set|unset|???)/ else $shline = $indent . $shline
# prepend $indent  to each returned line if /^(set|unset|???)/ else $shline = $indent . $shline
# 
# want:
# it would be nice to copy the permissions from the input file to the output
#
#
# sed script
##!/bin/sed -f
#done: 1 s/^#!\/bin\/csh/#!\/bin\/sh/
#done: s/\<setenv\> *\([^ 	]*\)[ 	]*/export \1; \1=/g
#done: s/\<set\> *\([^= 	]*\)[ 	]*=[ 	]*/\1=/g
#disagree: s/\<set\> *\([^ 	]*\)/\1=true/g
#done: s/\$\?\([A-Za-z0-9_]*\)/"$\1"/g
#done: s/\<if\> *(\(.*\)) *then\>/if [\1]; then/g
#done: s/\<if\> *(\(.*\))\>/[\1] \&\&/g
#done: s/\[\(.*\)==\(.*\)\]/[\1=\2]/g
#done: s/\[\(.*\)<=\(.*\)\]/[\1-le\2]/g
#done: s/\[\(.*\)>=\(.*\)\]/[\1-ge\2]/g
#done: s/\[\(.*\)<\(.*\)\]/[\1-lt\2]/g
#done: s/\[\(.*\)>\(.*\)\]/[\1-gt\2]/g
#done: s/\[\(.*\)&&\(.*\)\]/[\1-a\2]/g
#done: s/\[\(.*\)||\(.*\)\]/[\1-o\2]/g
#done: s/\<endif\>/fi/g
#done: s/\<case\> *\([^ :]*\) *:/\1)/g
#done: s/\<default\> *:/*)/g
#done: s/\<switch\> *(\(.*\))/case \1 in/g
#done: s/\<breaksw\>/;;/g
#done: s/\<endsw\>/esac/g
#done: s/\<source\>/./g
#done: s/\<foreach\> *\([^(]*\)(\(.*\))\>/for \1 in \2; do/g
#done: s/\<while\> *(\(.*\))\>/while [\1]; do/g
#done: s/\<end\>/done/g
#done: s/\\!/!/g
#done: s/ ~ / $HOME /g
#done: s/ ~\// $HOME\//g
#done: s/>\& *\([a-zA-Z_\$]*\)/> \1 2>\&1/g
#done: s/|\&/2>\&1 |/g
#done: s/$argv/$*/g
#done: s/$#argv/$#/g
#done: s/\([a-zA-Z]*\)=\$</read \1/g
#disagree: s/echo=true/set -x/g
#disagree: s/verbose=true/set -v/g
#disagree: s/\([^;]\) *;/\1;/g



