getopt with long options - sh

I have simple script to parse commandline arguments using getopts:
#!/bin/sh
while getopts ":ab" opt
do
case $opt in
a)
echo "received option: a"
;;
b)
echo "received option: b"
;;
?)
printf "\nunrecognized option\n\n" >&2
exit 2
;;
esac
done
shift $(( $OPTIND - 1 ))
echo $#
This works as expected.
I need to convert it to use the non-buildin function getopt , so that it can take long arguments:
#!/bin/sh
while getopt --options ":ab" --longoptions "after,before" -- "$#" opt
do
case $opt in
a|after)
echo "received option: a|after"
;;
b|before)
echo "received option: b|before"
;;
?)
printf "\nunrecognized option\n\n" >&2
exit 2
;;
esac
done
shift $(( $OPTIND - 1 ))
echo $#
The above code does not work, it stays in endless while loop.
What am I doing wrong ?
How can I use getopt with longoptions in my script ?

Related

sbt-native-packager: Scala App on Alpine Docker Image fails with permission denied

I have a Scala application that I want to run inside a Docker container. To build the docker image, I use sbt-native-packager.
The base image I am using is "openjdk:8-jre-alpine".
Tried "openjdk:8-jdk-alpine" - does not make any difference
Tried sbt-native-packager 1.3.20 - does not make any difference
project/plugins.sbt
resolvers += Resolver.typesafeRepo("releases")
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.17")
build.sbt
enablePlugins(JavaAppPackaging)
mainClass in Compile := Some("MyAppClass")
enablePlugins(DockerPlugin)
dockerBaseImage := "openjdk:8-jre-alpine" // startup fails with permission denied if using alpine :-(
Running a container with the resulting image leads to following error on startup:
docker run my-app:latest
docker: Error response from daemon: OCI runtime create failed: container_linux.go:345: starting container process caused "exec: \"/opt/docker/bin/my-app\": permission denied": unknown.
ERRO[0000] error waiting for container: context canceled
The app starts up normally when using "openjdk:8-jre".
Update
Contents of /opt/docker/bin/my-app:
#!/usr/bin/env bash
### ------------------------------- ###
### Helper methods for BASH scripts ###
### ------------------------------- ###
die() {
echo "$#" 1>&2
exit 1
}
realpath () {
(
TARGET_FILE="$1"
CHECK_CYGWIN="$2"
cd "$(dirname "$TARGET_FILE")"
TARGET_FILE=$(basename "$TARGET_FILE")
COUNT=0
while [ -L "$TARGET_FILE" -a $COUNT -lt 100 ]
do
TARGET_FILE=$(readlink "$TARGET_FILE")
cd "$(dirname "$TARGET_FILE")"
TARGET_FILE=$(basename "$TARGET_FILE")
COUNT=$(($COUNT + 1))
done
if [ "$TARGET_FILE" == "." -o "$TARGET_FILE" == ".." ]; then
cd "$TARGET_FILE"
TARGET_FILEPATH=
else
TARGET_FILEPATH=/$TARGET_FILE
fi
# make sure we grab the actual windows path, instead of cygwin's path.
if [[ "x$CHECK_CYGWIN" == "x" ]]; then
echo "$(pwd -P)/$TARGET_FILE"
else
echo $(cygwinpath "$(pwd -P)/$TARGET_FILE")
fi
)
}
# TODO - Do we need to detect msys?
# Uses uname to detect if we're in the odd cygwin environment.
is_cygwin() {
local os=$(uname -s)
case "$os" in
CYGWIN*) return 0 ;;
*) return 1 ;;
esac
}
# This can fix cygwin style /cygdrive paths so we get the
# windows style paths.
cygwinpath() {
local file="$1"
if is_cygwin; then
echo $(cygpath -w $file)
else
echo $file
fi
}
# Make something URI friendly
make_url() {
url="$1"
local nospaces=${url// /%20}
if is_cygwin; then
echo "/${nospaces//\\//}"
else
echo "$nospaces"
fi
}
# This crazy function reads in a vanilla "linux" classpath string (only : are separators, and all /),
# and returns a classpath with windows style paths, and ; separators.
fixCygwinClasspath() {
OLDIFS=$IFS
IFS=":"
read -a classpath_members <<< "$1"
declare -a fixed_members
IFS=$OLDIFS
for i in "${!classpath_members[#]}"
do
fixed_members[i]=$(realpath "${classpath_members[i]}" "fix")
done
IFS=";"
echo "${fixed_members[*]}"
IFS=$OLDIFS
}
# Fix the classpath we use for cygwin.
fix_classpath() {
cp="$1"
if is_cygwin; then
echo "$(fixCygwinClasspath "$cp")"
else
echo "$cp"
fi
}
# Detect if we should use JAVA_HOME or just try PATH.
get_java_cmd() {
if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
echo "$JAVA_HOME/bin/java"
else
echo "java"
fi
}
echoerr () {
echo 1>&2 "$#"
}
vlog () {
[[ $verbose || $debug ]] && echoerr "$#"
}
dlog () {
[[ $debug ]] && echoerr "$#"
}
execRunner () {
# print the arguments one to a line, quoting any containing spaces
[[ $verbose || $debug ]] && echo "# Executing command line:" && {
for arg; do
if printf "%s\n" "$arg" | grep -q ' '; then
printf "\"%s\"\n" "$arg"
else
printf "%s\n" "$arg"
fi
done
echo ""
}
# we use "exec" here for our pids to be accurate.
exec "$#"
}
addJava () {
dlog "[addJava] arg = '$1'"
java_args+=( "$1" )
}
addApp () {
dlog "[addApp] arg = '$1'"
app_commands+=( "$1" )
}
addResidual () {
dlog "[residual] arg = '$1'"
residual_args+=( "$1" )
}
addDebugger () {
addJava "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$1"
}
require_arg () {
local type="$1"
local opt="$2"
local arg="$3"
if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then
die "$opt requires <$type> argument"
fi
}
is_function_defined() {
declare -f "$1" > /dev/null
}
# Attempt to detect if the script is running via a GUI or not
# TODO - Determine where/how we use this generically
detect_terminal_for_ui() {
[[ ! -t 0 ]] && [[ "${#residual_args}" == "0" ]] && {
echo "true"
}
# SPECIAL TEST FOR MAC
[[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]] && [[ "${#residual_args}" == "0" ]] && {
echo "true"
}
}
# Processes incoming arguments and places them in appropriate global variables. called by the run method.
process_args () {
local no_more_snp_opts=0
while [[ $# -gt 0 ]]; do
case "$1" in
--) shift && no_more_snp_opts=1 && break ;;
-h|-help) usage; exit 1 ;;
-v|-verbose) verbose=1 && shift ;;
-d|-debug) debug=1 && shift ;;
-no-version-check) no_version_check=1 && shift ;;
-mem) echo "!! WARNING !! -mem option is ignored. Please use -J-Xmx and -J-Xms" && shift 2 ;;
-jvm-debug) require_arg port "$1" "$2" && addDebugger $2 && shift 2 ;;
-main) custom_mainclass="$2" && shift 2 ;;
-java-home) require_arg path "$1" "$2" && jre=`eval echo $2` && java_cmd="$jre/bin/java" && shift 2 ;;
-D*|-agentlib*|-XX*) addJava "$1" && shift ;;
-J*) addJava "${1:2}" && shift ;;
*) addResidual "$1" && shift ;;
esac
done
if [[ no_more_snp_opts ]]; then
while [[ $# -gt 0 ]]; do
addResidual "$1" && shift
done
fi
is_function_defined process_my_args && {
myargs=("${residual_args[#]}")
residual_args=()
process_my_args "${myargs[#]}"
}
}
# Actually runs the script.
run() {
# TODO - check for sane environment
# process the combined args, then reset "$#" to the residuals
process_args "$#"
set -- "${residual_args[#]}"
argumentCount=$#
#check for jline terminal fixes on cygwin
if is_cygwin; then
stty -icanon min 1 -echo > /dev/null 2>&1
addJava "-Djline.terminal=jline.UnixTerminal"
addJava "-Dsbt.cygwin=true"
fi
# check java version
if [[ ! $no_version_check ]]; then
java_version_check
fi
if [ -n "$custom_mainclass" ]; then
mainclass=("$custom_mainclass")
else
mainclass=("${app_mainclass[#]}")
fi
# Now we check to see if there are any java opts on the environment. These get listed first, with the script able to override them.
if [[ "$JAVA_OPTS" != "" ]]; then
java_opts="${JAVA_OPTS}"
fi
# run sbt
execRunner "$java_cmd" \
${java_opts[#]} \
"${java_args[#]}" \
-cp "$(fix_classpath "$app_classpath")" \
"${mainclass[#]}" \
"${app_commands[#]}" \
"${residual_args[#]}"
local exit_code=$?
if is_cygwin; then
stty icanon echo > /dev/null 2>&1
fi
exit $exit_code
}
# Loads a configuration file full of default command line options for this script.
loadConfigFile() {
cat "$1" | sed $'/^\#/d;s/\r$//'
}
# Now check to see if it's a good enough version
# TODO - Check to see if we have a configured default java version, otherwise use 1.6
java_version_check() {
readonly java_version=$("$java_cmd" -version 2>&1 | awk -F '"' '/version/ {print $2}')
if [[ "$java_version" == "" ]]; then
echo
echo No java installations was detected.
echo Please go to http://www.java.com/getjava/ and download
echo
exit 1
else
local major=$(echo "$java_version" | cut -d'.' -f1)
if [[ "$major" -eq "1" ]]; then
local major=$(echo "$java_version" | cut -d'.' -f2)
fi
if [[ "$major" -lt "6" ]]; then
echo
echo The java installation you have is not up to date
echo $app_name requires at least version 1.6+, you have
echo version $java_version
echo
echo Please go to http://www.java.com/getjava/ and download
echo a valid Java Runtime and install before running $app_name.
echo
exit 1
fi
fi
}
### ------------------------------- ###
### Start of customized settings ###
### ------------------------------- ###
usage() {
cat <<EOM
Usage: $script_name [options]
-h | -help print this message
-v | -verbose this runner is chattier
-d | -debug set sbt log level to debug
-no-version-check Don't run the java version check.
-main <classname> Define a custom main class
-jvm-debug <port> Turn on JVM debugging, open at the given port.
# java version (default: java from PATH, currently $(java -version 2>&1 | grep version))
-java-home <path> alternate JAVA_HOME
# jvm options and output control
JAVA_OPTS environment variable, if unset uses "$java_opts"
-Dkey=val pass -Dkey=val directly to the java runtime
-J-X pass option -X directly to the java runtime
(-J is stripped)
# special option
-- To stop parsing built-in commands from the rest of the command-line.
e.g.) enabling debug and sending -d as app argument
\$ ./start-script -d -- -d
In the case of duplicated or conflicting options, basically the order above
shows precedence: JAVA_OPTS lowest, command line options highest except "--".
Available main classes:
MyAppClass
EOM
}
### ------------------------------- ###
### Main script ###
### ------------------------------- ###
declare -a residual_args
declare -a java_args
declare -a app_commands
declare -r real_script_path="$(realpath "$0")"
declare -r app_home="$(realpath "$(dirname "$real_script_path")")"
# TODO - Check whether this is ok in cygwin...
declare -r lib_dir="$(realpath "${app_home}/../lib")"
declare -a app_mainclass=(MyAppClass)
declare -r script_conf_file="${app_home}/../conf/application.ini"
declare -r app_classpath="$lib_dir/my-app-0.1.0-SNAPSHOT.jar:$lib_dir/org.scala-lang.scala-library-2.12.8.jar:$lib_dir/com.thenewmotion.ocpp.ocpp-j-api_2.12-9.0.1.jar:$lib_dir/com.thenewmotion.ocpp.ocpp-messages_2.12-9.0.1.jar:$lib_dir/com.thenewmotion.enum-utils_2.12-0.2.1.jar:$lib_dir/com.thenewmotion.ocpp.ocpp-json_2.12-9.0.1.jar:$lib_dir/org.json4s.json4s-native_2.12-3.6.1.jar:$lib_dir/org.json4s.json4s-core_2.12-3.6.1.jar:$lib_dir/org.json4s.json4s-ast_2.12-3.6.1.jar:$lib_dir/org.json4s.json4s-scalap_2.12-3.6.1.jar:$lib_dir/com.thoughtworks.paranamer.paranamer-2.8.jar:$lib_dir/org.slf4j.slf4j-api-1.7.25.jar:$lib_dir/org.java-websocket.Java-WebSocket-1.3.9.jar:$lib_dir/org.apache.logging.log4j.log4j-api-2.11.2.jar:$lib_dir/org.apache.logging.log4j.log4j-core-2.11.2.jar:$lib_dir/org.apache.logging.log4j.log4j-slf4j-impl-2.11.2.jar:$lib_dir/com.typesafe.config-1.3.4.jar"
# java_cmd is overrode in process_args when -java-home is used
declare java_cmd=$(get_java_cmd)
# if configuration files exist, prepend their contents to $# so it can be processed by this runner
[[ -f "$script_conf_file" ]] && set -- $(loadConfigFile "$script_conf_file") "$#"
run "$#"
run the sbt task docker:stage. Then analyze the output created in the folder target/docker/stage.
In my case the Dockerfile contains the following:
FROM openjdk:11-jre-slim as stage0
WORKDIR /opt/docker
COPY opt /opt
USER root
RUN ["chmod", "-R", "u=rX,g=rX", "/opt/docker"]
RUN ["chmod", "u+x,g+x", "/opt/docker/bin/sample"]
FROM openjdk:11-jre-slim
LABEL MAINTAINER="your name"
USER root
RUN id -u demiourgos728 2> /dev/null || useradd --system --create-home --uid 1001 --gid 0 demiourgos728
WORKDIR /opt/docker
COPY --from=stage0 --chown=demiourgos728:root /opt/docker /opt/docker
EXPOSE 9000
USER 1001
ENTRYPOINT ["/opt/docker/bin/sample"]
CMD []
I had the problem that the PID file could not be created. I think in your case it will be something similar. There is no magic involved here.
The folder /opt/docker does not have write permissions by default. As the documentation states, you could add the following line to your build.sbt:
dockerAdditionalPermissions += (DockerChmodType.UserGroupWriteExecute, "/opt/docker")
which will add an additional line:
RUN ["chmod", "u=rwX,g=rwX", "/opt/docker"]
to the stage0 container. See nativer packager docs.
Alternatively, disable the PID file by passing a parameter to the JVM:
bashScriptExtraDefines ++= Seq( "addJava '-Dpidfile.path=/dev/null'" )
to your build.sbt. Play Production configuration Docs

posix sh: how to count number of occurrences in a string without using external tools?

In bash, it can be done like this:
#!/bin/bash
query='bengal'
string_to_search='bengal,toyger,bengal,persian,bengal'
delimiter='|'
replace_queries="${string_to_search//"$query"/"$delimiter"}"
delimiter_count="${replace_queries//[^"$delimiter"]}"
delimiter_count="${#delimiter_count}"
echo "Found $delimiter_count occurences of \"$query\""
Output:
Found 3 occurences of "bengal"
The caveat of course is that the delimiter cannot occur in 'query' or 'string_to_search'.
In POSIX sh, string replacement is not supported. Is there a way this can be done in POSIX sh using only shell builtins?
#!/bin/sh
query='bengal'
string_to_search='bengal,toyger,bengal,persian,bengal'
ct() (
n=0
IFS=,
q=$1
set $2
for t in "$#"; do
if [ "$t" = "$q" ]; then
n=$((n + 1))
fi
done
echo $n
)
n=$(ct "$query" "$string_to_search")
printf "found %d %s\n" $n $query
Though I'm not sure what the point is. If you've got a posix shell,
you also almost certainly have printf, sed, grep, and wc.
printf '%s\n' "$string_to_search" | sed -e 's/,/\n/g' | grep -Fx "$query" | wc -l
Think I got it...
#!/bin/sh
query='bengal'
string_to_search='bengal,toyger,bengal,persian,bengal'
i=0
process_string="$string_to_search"
while [ -n "$process_string" ]; do
case "$process_string" in
*"$query"*)
process_string="${process_string#*"$query"}"
i="$(( i + 1 ))"
;;
*)
break
;;
esac
done
echo "Found $i occurences of \"$query\""

Iterate over $# stored in another variable in another function

How can I iterate over $# after it has been stored in another variable in another function?
Note this is about the sh shell, not bash.
My code (super simplified):
#! /bin/sh
set -- a b "c d"
args=
argv() {
shift # pretend handling options
args="$#" # remaining arguments
}
fun() {
for arg in "$args"; do
echo "+$arg+"
done
}
argv "$#"
fun
Output:
+b c d+
I want:
+b+
+c d+
The special variable $# stores argv preserving whitespace. The for loop can loop over $# also preserving whitespace.
set -- a b "c d"
for arg in "$#"; do
echo "+$arg+"
done
Output:
+a+
+b+
+c d+
But once $# is assigned to another variable the whitespace preserving is gone.
set -- a b "c d"
args="$#"
for arg in "$args"; do
echo "+$arg+"
done
Output
+a b c d+
Without quotes:
for arg in $args; do
echo "+$arg+"
done
Output:
+a+
+b+
+c+
+d+
In bash it can be done using arrays.
set -- a b "c d"
args=("$#")
for arg in "${args[#]}"; do
echo "+$arg+"
done
Output:
+a+
+b+
+c d+
Can that be done in the sh shell?
You could use shift again inside fun if you know the shift has been performed in argv.
#! /bin/sh
set -- a b "c d"
args=
argv() {
shifted=1 # pretend handling options
shift $shifted
}
fun() {
[ -n $shifted ] && shift $shifted
for arg; do
echo "+$arg+"
done
}
argv "$#"
fun "$#"
Output:
+b+
+c d+
Here are two workarounds. Both have caveats.
First workaround: put newlines between arguments then use read.
set -- a b " c d "
args=
argv() {
shift
for arg in "$#"; do
args="$args$arg\n"
done
}
fun() {
printf "$args" | while IFS= read -r arg; do
echo "+$arg+"
done
}
argv "$#"
fun
Output:
+b+
+ c d +
Note that even the spaces before and after are preserved.
Caveat: if the arguments contain newlines you are screwed.
Second workaround: put quotes around arguments then use eval.
set -- a b " c d "
args=
argv() {
shift
for arg in "$#"; do
args="$args \"$arg\""
done
}
fun() {
for arg in "$#"; do
echo "+$arg+"
done
}
argv "$#"
eval fun "$args"
Caveat: if the arguments contain quotes you are screwed.

Creating autocomplete script with sub commands

I'm trying to create an autocomplete script for use with fish; i'm porting over a bash completion script for the same program.
The program has three top level commands, say foo, bar, and baz and each has some subcommands, just say a b and c for each.
What I'm seeing is that the top level commands auto complete ok, so if I type f I'm getting foo to autocomplete, but then if I hit tab again to see what it's sub commands are, i see foo, bar, baz, a, b, c and it should just be a, b, c
I am using as a reference the git completion script since it seems to work right. I am also using the git flow script as a reference as well.
I think this is handled in the git completion script by:
function __fish_git_needs_command
set cmd (commandline -opc)
if [ (count $cmd) -eq 1 -a $cmd[1] = 'git' ]
return 0
end
return 1
end
Which makes sense, you can only use the completion if there is a single arg to the command, the script itself; if you use that as the condition (-n) for the call to complete on the top level commands, I think the right thing would happen.
However, what I'm seeing is not the case. I copied that function over to my script, changed "git" appropriately, and did not have any luck.
The trimmed down script is as follows:
function __fish_prog_using_command
set cmd (commandline -opc)
set subcommands $argv
if [ (count $cmd) = (math (count $subcommands) + 1) ]
for i in (seq (count $subcommands))
if not test $subcommands[$i] = $cmd[(math $i + 1)]
return 1
end
end
return 0
end
return 1
end
function __fish_git_needs_command
set cmd (commandline -opc)
set startsWith (echo "$cmd[1]" | grep -E 'prog$')
# there's got to be a better way to do this regex, fish newb alert
if [ (count $cmd) = 1 ]
# Is this always false? Is this the problem?
if [ $cmd[1] -eq $cmd[1] ]
return 1
end
end
return 0
end
complete --no-files -c prog -a bar -n "__fish_git_needs_command"
complete --no-files -c prog -a foo -n "__fish_git_needs_command"
complete --no-files -c prog -a a -n "__fish_prog_using_command foo"
complete --no-files -c prog -a b -n "__fish_prog_using_command foo"
complete --no-files -c prog -a c -n "__fish_prog_using_command foo"
complete --no-files -c prog -a baz -n "__fish_git_needs_command"
Any suggestions on how to make this work is much appreciated.
I guess you are aware that return 0 means true and that return 1 means false?
From your output it looks like your needs_command function is not working properly, thus showing bar even when it has subcommands.
I just tried the following code and it works as expected:
function __fish_prog_needs_command
set cmd (commandline -opc)
if [ (count $cmd) -eq 1 -a $cmd[1] = 'prog' ]
return 0
end
return 1
end
function __fish_prog_using_command
set cmd (commandline -opc)
if [ (count $cmd) -gt 1 ]
if [ $argv[1] = $cmd[2] ]
return 0
end
end
return 1
end
complete -f -c prog -n '__fish_prog_needs_command' -a bar
complete -f -c prog -n '__fish_prog_needs_command' -a foo
complete -f -c prog -n '__fish_prog_using_command foo' -a a
complete -f -c prog -n '__fish_prog_using_command foo' -a b
complete -f -c prog -n '__fish_prog_using_command foo' -a c
complete -f -c prog -n '__fish_prog_needs_command' -a baz
Output from completion:
➤ prog <Tab>
bar baz foo
➤ prog foo <Tab>
a b c
➤ prog foo
Is this what you want?

svn diff through perltidy

I want to run perltidy before i look for diff in my subversion working copy.
in svn config i wrote:
diff-cmd = /usr/bin/d.sh
As David W said in this answer https://stackoverflow.com/a/5834900/1927848 i make a script /usr/bin/d.sh:
#!/usr/local/bin/bash
/usr/local/bin/perltidy "$1" > "/tmp/$1"
/usr/local/bin/perltidy "$2" > "/tmp/$2"
/usr/bin/diff "$1" "$2"
/bin/rm "/tmp/$1" "/tmp/$2"
exit 0
and when i make svn diff in working copy i got some errors:
dev# svn diff
Index: nodeny/new_month.pl
===================================================================
Unknown option: u
Error on command line; for help try 'perltidy -h'
Option l is ambiguous (libpods, line-up-parentheses, logfile, logfile-gap, long-block-line-count, look-for-autoloader, look-for-hash-bang, look-for-selfloader)
Error on command line; for help try 'perltidy -h'
diff: option requires an argument -- L
/usr/bin/diff: Try `/usr/bin/diff --help' for more information.
where is my errors?
UPD: $1 and $2 are not file names, $6 and $7 contains file names. i made some modifications to code, thanks to ikegami comment
#!/usr/local/bin/bash
/usr/local/bin/perltidy "$6" -st > "/tmp/tidy001"
/usr/local/bin/perltidy "$7" -st > "/tmp/tidy002"
/usr/bin/diff "/tmp/tidy001" "/tmp/tidy002"
/bin/rm "/tmp/tidy001" "/tmp/tidy002"
exit 0
but now script only does first perltidy command and wait... whats wrong?
UPD2: perl script, that works:
#!/usr/bin/perl
use Text::Diff;
if (-e $ARGV[-2] && -e $ARGV[-1]) {
my $str1 = `/usr/local/bin/perltidy -npro -pbp -nst -se -et=4 -bar -l=200 $ARGV[-2] -st`;
my $str2 = `/usr/local/bin/perltidy -npro -pbp -nst -se -et=4 -bar -l=200 $ARGV[-1] -st`;
my $diff = diff(\$str1, \$str2);
print $diff;
}
else {
print "Error file $ARGV[-2] or $ARGV[-1] not exists\n";
}
exit 0;
I'm not an experienced bash code, so the following may not be optimal, especially given the redundancy, but it solves your problem by assuming the last two args are the file names.
#!/bin/bash
args=("$#")
f1_idx=$(( ${#args[#]} - 2 ))
f1="${args[$f1_idx]}"
/usr/local/bin/perltidy "$f1" -st > "/tmp/$f1"
args[$f1_idx]="/tmp/$f1"
f2_idx=$(( ${#args[#]} - 1 ))
f2="${args[$f2_idx]}"
/usr/local/bin/perltidy "$f2" -st > "/tmp/$f2"
args[$f2_idx]="/tmp/$f2"
/usr/bin/diff "$args[#]"
/bin/rm "/tmp/$f1" "/tmp/$f2"
exit 0
Or if you don't care about the actual file names (as your update implies), you can avoid the temporary files altogether.
#!/bin/bash
args=("$#")
last_idx=$(( ${#args[#]} - 1 ))
f2="${args[$last_idx]}"
unset args[$last_idx]
last_idx=$(( ${#args[#]} - 1 ))
f1="${args[$last_idx]}"
unset args[$last_idx]
/usr/bin/diff "$args[#]" \
<( /usr/local/bin/perltidy "$f1" -st ) \
<( /usr/local/bin/perltidy "$f2" -st )
exit 0