Why the results of these two code fragments are not the same?

Code 1:


#!/bin/sh

for arg1 in "$@"
do
    counter=0

   for arg2 in "$@"
   do

     if [ "$arg2" = "$arg1" ] && [ $counter -eq 0 ]
     then
       counter=$((counter+1))

       continue 
    fi


   if [ "$arg2" = "$arg1" ]
   then
    
     echo
     echo "Error: Two or more arguments are the same."
     echo
     echo "Exiting..."
     echo

     exit 1
    fi

  done

done

exit 0

It outputs:

[john@manjaromatepc Downloads]$ ./script.sh ~/Documents/ ~/Downloads/
[john@manjaromatepc Downloads]$ 

Code 2:

#!/bin/sh


for arg1 in "$@"
do
  
  counter=0

  for arg2 in "$@"
  do


    if [ $counter -eq 0 ]
    then
  
      counter=$((counter+1))

      continue 
   fi


  if [ "$arg2" = "$arg1" ]
  then
    
    echo
    echo "Error: Two or more arguments are the same."
    echo
    echo "Exiting..."
    echo

    exit 1
  fi

 done

done

exit 0

It outpouts:

 
[john@manjaromatepc Downloads]$ ./script.sh ~/Documents/ ~/Downloads/

Error: Two or more arguments are the same.

Exiting...

[john@manjaromatepc Downloads]$ 

I found the issue, the second code fragment has a logical mistake.

Maybe...

Since both of your scripts are comparing every element of the operand list to every element of the operand list, one might expect that there would be at least two cases where the operands would be identical. Those cases would be when you're comparing the first operand to the first operand (i.e., $HOME/Documents/ to $HOME/Documents/ ) and when you're comparing the second operand to the second operand (i.e., $HOME/Downloads/ to $HOME/Downloads/ ).

Maybe there is a logical mistake in both scripts???

What is the logic in both of your scripts behind the variable named counter ? Why does it matter what the value of $counter is when trying to determine whether or not two command-line arguments are the same?

1 Like

The POSIX shell code detects if there are two or more, same command line arguments to the script.

An example with C++03.

Assuming we have a vector (array) with some strings:

#include <iostream>
#include <string>
#include <vector>


int main()
{
  using namespace std;

  string stringArray[]={"some", "to", "words", "to", "use", "for", "testing", "purposes"};

  vector<string> myvector(stringArray, stringArray+ sizeof(stringArray)/sizeof(*stringArray) );


  Here is the interesting part:

  for(vector<string>::size_type i= 0; i< myvector.size(); ++i)
  {
    for(vector<string>::size_type j= i+ 1; j< myvector.size(); ++j)
    {
       if(myvector[j]== myvector)
       {
         cout<< "Two or more words are the same\n\n";
         
         return 0;
       }
    }
  }

}

This is what I am trying to do with the POSIX shell code I posted.

But, the code you've written in your shell scripts acts more like your C++ code with the inner loop changed from:

    for(vector<string>::size_type j= i+ 1; j< myvector.size(); ++j)

to:

    for(vector<string>::size_type j= 0; j< myvector.size(); ++j)

There doesn't appear to be any attempt to keep from checking one element against itself in your command-line argument vector in either of your shell scripts.

I repeat: "What is the logic in both of your scripts behind the variable named counter ? Why does it matter what the value of $counter is when trying to determine whether or not two command-line arguments are the same?"

Yes you are right.

When the shell script starts, both arg1 and arg2 have the same value.

So I use the counter with value 0, to determine this case, and advance the second for-loop to the next argument, and from there, I check if arg2=arg1.

If the second loop ends, the first loop progresses to the next argument.

The second loop restarts again with the first argument, and proceeds until it meets the first loop, where arg2=arg1 and counter -eq 0.

arg2 then proceeds to the next argument, until it ends, if no duplicate arguments found.

Then the first loop progresses to the next argument. etc.

The code that works, is the first I posted, and is this:

#!/bin/sh

for arg1 in "$@"
do
    counter=0


   for arg2 in "$@"
   do

     if [ "$arg2" = "$arg1" ] && [ $counter -eq 0 ]
     then
       counter=$((counter+1))

       continue 
    fi


   if [ "$arg2" = "$arg1" ]
   then
    
     echo
     echo "Error: Two or more arguments are the same."
     echo
     echo "Exiting..."
     echo

     exit 1
    fi

  done

done

exit 0

Maybe the entire code could be condensed down to (tested with bash --posix )

TMPARR=( $@ )
for ((i=0; i<$#; i++))
  do    for ((j=i+1; j<$#; j++))
          do    [ ${TMPARR} = ${TMPARR[j]} ] && { echo same arguments; exit 1; }
          done
  done
1 Like

I generally avoid eval , but have you considered making your shell script more like your C++ code?

#!/bin/sh
#set -xv
i=1
while [ $i -le $# ]
do	j=$((i + 1))
	while [ $j -le $# ]
	do	if eval [ \"\${$i}\" = \"\${$j}\" ]
		then	printf '\n%s (%d,%d)\n\nExiting...\n\n' \
			    'Error: Two or more arguments are the same:' \
			    $i $j >&2
			exit 1
		fi
		j=$((j + 1))
	done
	i=$((i + 1))
done

The while loops in the above script could be simplified to for loops that are accepted by many shells (including recent versions of bash and ksh ) but they are extensions to the standards and, therefore, not available in several shells. The above code just uses standard utility interfaces and shell language constructs. For shells that accept these extensions, the below code looks even more like your C++ code.

#!/bin/sh
set -xv
for ((i = 1; i <= $#; i++))
do	for ((j = i + 1; j <= $#; j++))
	do	if eval [ \"\${$i}\" = \"\${$j}\" ]
		then	printf '\n%s (%d,%d)\n\nExiting...\n\n' \
			    "Error: Two or more arguments are the same:" \
			    $i $j >&2
			exit 1
		fi
	done
done

Note that both of these script write their error messages to the standard error output instead of to standard output and both of these script print the argument numbers of the first found pair of identical argument in their diagnostics.

Thank you for your reply.

The code is not POSIX shell compliant. POSIX shell has not arrays, and not this kind of for loop.

I am in the half of my introductory POSIX shell book, so there are a lot of things I do not know.

But there is this. In POSIX shell all data are strings, and strings are a kind of array.

Here is an example:

#!/bin/sh

text="This is some text"

for x in $text
do
  # Do stuff with x.
done

exit 0

--- Post updated at 02:27 PM ---

Thank you for your reply.

I do not know anything about set yet.

I do not know anything about eval yet.

OK, thank you for your reply. There are things that I do not know yet, and I will keep them in mind.

For now, I think the code I posted works OK.