John Threepwood
John Threepwood

Reputation: 16143

How to know if file in a loop is the last one?

Example

for FILE in $DIR/* 
do
  if(<is last File>)
    doSomethingSpecial($FILE)
  else
    doSomethingRegular($FILE)
  fi
done

What to call for <is last file> to check if the current file is the last one in the array ?

Is there an easy built-in check without checking the array's length by myself ?

Upvotes: 26

Views: 30705

Answers (8)

yatsek
yatsek

Reputation: 1015

It's old question - but building on answer from @GregReynolds please use this one-liner if commands differ only by parameters on last pass. Ugly, ugly code for one-liner lovers

( ff="" ; for f in * "" ; do [ -n "$ff" ] && echo $(${f:+false} && echo $ff alternate params here || echo normal params $ff ) ; ff=$f ; done ) normal params 1 normal params 2 normal params 3 4 alternate params here

Upvotes: 1

jimbobmcgee
jimbobmcgee

Reputation: 1721

Building on the current highest-voted answer from @cdarke (https://stackoverflow.com/a/12298757/415523), if looking at a general array of values (rather than specifically files on disk), the loop code would be as follows:

declare -a array
declare -i length current

array=( a b c d e c )
length=${#array[@]}
current=0

for VALUE in "${array[@]}"; do 
  current=$((current + 1))

  if [[ "$current" -eq "$length" ]]; then
     echo "$VALUE is the last" 
  else 
     echo "$VALUE"
  fi 
done

This yields the output:

a
b
c
d
e
c is the last

This ensures that only the last item in the array triggers the alternative action and that, if any other item in the array duplicates the last value, the alternative action is not called for the earlier duplicates.

In the case of an array of paths to files in a specific directory, e.g.

array=( $DIR/* )

...it is probably less of a concern, since individual filenames within the same directory are almost-certainly unique (unless you have a really odd filesystem!)

Upvotes: 4

cdarke
cdarke

Reputation: 44374

What to call for to check if the current file is the last one in the array ?

For a start, you are not using an array. If you were then it would be easy:

declare -a files
files=($DIR/*)
pos=$(( ${#files[*]} - 1 ))
last=${files[$pos]}

for FILE in "${files[@]}"
do 
  if [[ $FILE == $last ]]
  then
     echo "$FILE is the last" 
     break
  else 
     echo "$FILE"
  fi 
done 

Upvotes: 22

Ubuntuser
Ubuntuser

Reputation: 405

You can use find to find the total number of files. Then when you are in the loop count to the total number and carry out your task when the total equals the count i.e, the last file.

 f=0
tot_files=`find  . -iname '*.txt' | wc -l`
 for FILE in $DIR/* 
do 
f=($f+1)
if [[ $f == $tot_files ]];then
carryout your task
fi
done

Upvotes: 3

David W.
David W.

Reputation: 107060

What makes a file the last one? Is there something special about it? Is it the file with the greatest name when sorted by name?

Maybe you can take the file names backwards. Then, it's the first file you want to treat special and not the last. figuring out the first is a much easier task than doing the last:

for file in $(ls -r1 $dir)
do
    if [ ! $processedLast ] 
    then
        doSomethingSpecial($file)
        processedLast=1
    else
        doSomethingRegular($file)
    fi
done

No arrays needed. Actually, I like chepner's answer about using positional parameters.

Upvotes: 2

chepner
chepner

Reputation: 531708

You can abuse the positional parameters, since they act similarly to an array, but are a little easier to manipulate. You should either save the old positional parameters, or execute in a subshell. 

# Method 1: use a subshell. Slightly cleaner, but you can't always
# do this (for example, you may need to affect variables in the current
# shell
files=( $DIR/* )

(
    set -- "${files[@]}"
    until (( $# == 1 )); do
        doSomethingRegular "$1"
        shift
    done    
    doSomethingSpecial "$1"
)

# Method 2: save the positional parameters. A bit uglier, but
# executes everything in the same shell.

files=( $DIR/* )
oldPP=( "$@" )
set -- "${files[@]}"
until (( $# == 1 )); do
    doSomethingRegular "$1"
    shift
done    
doSomethingSpecial "$1"
set -- "${oldPP[@]}"

Upvotes: 2

Greg Reynolds
Greg Reynolds

Reputation: 10216

Try this

LAST_FILE=""
for f in *
do
        if [ ! -z $LAST_FILE ]
        then
                echo "Process file normally $LAST_FILE"
        fi
        LAST_FILE=$f
done
if [ ! -z $LAST_FILE ]
then
        echo "Process file as last file $LAST_FILE"
fi

Produces

bash[1051]: ls
1  2  3  4
bash[1052]: sh ../last_file.sh
Process file normally 1
Process file normally 2
Process file normally 3
Process file as last file 4

Upvotes: 5

camh
camh

Reputation: 42478

I know of no way to tell that you are processing the last element of a list in a for loop. However you could use an array, iterate over all but the last element, and then process the last element outside the loop:

files=($DIR/*)
for file in "${files[@]::${#files[@]}-1}" ; do
    doSomethingRegular "$file"
done
doSomethingSpecial "${files[@]: -1:1}"

The expansion ${files[@]:offset:length} evaluates to all the elements starting at offset (or the beginning if empty) for length elements. ${#files[@]}-1 is the number of elements in the array minus 1.

${files[@]: -1:1} evaluates to the last element - -1 from the end, length 1. The space is necessary as :- is treated differently to : -.

Upvotes: 10

Related Questions