Dominique M
Dominique M

Reputation: 305

Using a loop to select items in a directory

I have a directory with several large files. Every file comes in a pair, and I would like to use a bash loop to every time select two files, run a command line tool on them and then move on to the next pair of files.

My directory would be like: file1, file2, file3, file4, file5, file6

I would then take file1 and file2, do something, take file3 and file4, do something etc.

I only managed to do this for a single file:

for file_name in dir_name; do something; done

Upvotes: 0

Views: 1136

Answers (6)

ghoti
ghoti

Reputation: 46846

I'm in the "use an array" camp, but I wouldn't populate or parse the array using external tools like find or seq. Everything you need is in bash already.

files=( * )

for ((i=0; i<${#files[@]}; i+=2)); do
  printf '%s / %s\n' "${files[i]}" "${files[$((i+1))]}"
done

Upvotes: 0

dash-o
dash-o

Reputation: 14452

Going back to basic for loop, no array, etc. Will work for any shell (does not rely on bash features).

Capture the name of the first file in a pair, and execute the command when on the second file.

first=
for file_name in dir_name/* ; do
    if [ "$first" ] ; then
        # 2nd entry - pair
        do-something "$first" "$file_name"
        first=
    else
        # First entry - just remember.
        first=$file_name
    fi
done

Upvotes: 1

tshiono
tshiono

Reputation: 22012

If you want to be strict about handling special characters in filenames, how about:

while IFS= read -r -d "" f; do
    ary+=("$f")
done < <(find "dir_name" -type f -print0 | sort -z)

for ((i=0; i<${#ary[@]}; i+=2 )); do
    echo "${ary[i]}" "${ary[i+1]}"
    # or some_command "${ary[i]}" "${ary[i+1]}"
done

It allows the filenames to contain whitespace, tab, newline, quote, or any other special characters.
(Although some people dislike this kind of serious approach :-/)

Hope this helps.

Upvotes: 0

root
root

Reputation: 6048

Assuming that your file names contain no spaces or quotes:

ls dir_name \
    | xargs -L 2 \
    | while read FILE1 FILE2; do \
        printf "file1 %s file2 %s\n" "$FILE1" "$FILE2"
    done

Example:

$ ls
a  b  c  d  e  f
$ ls . \
    | xargs -L 2 \
    | while read FILE1 FILE2; do \
        printf "file1 %s file2 %s\n" "$FILE1" "$FILE2"
    done
file1 a file2 b
file1 c file2 d
file1 e file2 f

Note 1:

Because of the pipes, each execution of printf is in another shell process. Setting variables won't apply across loop iterations. If you want to do that, you can read all the lines into an array with

readarray -t FILES < <(ls dir_name | xargs -L 2)

and then iterate the array with

COUNT=0
for LINE in "${FILES[@]}"; do
    FILE1="${LINE%% *}"
    FILE2="${LINE##* }"
    printf "file1 %s file2 %s\n" "$FILE1" "$FILE2"
    ((COUNT++))
done

This allows you to set variables e.g. COUNT across iterations, however, it takes more memory and stops working when you want the files in triplets.

You can use an array for an arbitrary tuple of files:

COUNT=0
for LINE in "${FILES[@]}"; do
    TUPLE=( $LINE ) # note: no quotes
    printf "file1 %s file2 %s\n" "${TUPLE[0]}" "${TUPLE[1]}"
    ((COUNT++))
done

Note 2:

You can also use readarray's callback mechanism:

COUNT=0
callback() 
{ 
    TUPLE=( $2 ) # note: no quotes
    printf "file1 %s file2 %s\n" "${TUPLE[0]}" "${TUPLE[1]}"
    ((COUNT++))
}
...
readarray -t -C callback -c 1 FILES < <(ls dir_name | xargs -L 2)

Upvotes: 0

Matias Barrios
Matias Barrios

Reputation: 5056

If you know the name of the file and the amount of them you can simply do this :

#!/bin/bash

limit=20
for ((i=0; i < limit; i+=2 )) {
    echo "file${i} file$(( i + 1))"
}

Output

file0 file1
file2 file3
file4 file5
file6 file7
file8 file9
file10 file11
file12 file13
file14 file15
file16 file17
file18 file19 

Assuming you do not know the name of the files, you can use this Ruby script :

#!/usr/bin/ruby

require 'find'

search_in='.'
files = []
Find.find(search_in) do |path|
  files << path if path =~ /.*\.txt$/
end

files.sort_by!{|f| f.scan(/[0-9]+/)[0].to_i }

files.each_slice(2) do |a, b|
    system("echo #{a} #{b}")
end

And just change echo for whatever you want.

Hope it helps!

Upvotes: 0

nullPointer
nullPointer

Reputation: 4574

One way would be to put them in an array and build pairs as below :

files=(*)  ## assuming you're in current dir
for i in $(seq 0 $((${#files[@]}-1)))
do 
     if [[ $(( $i % 2)) == 0 ]]  
     then 
        pair="${files[$i]} ${files[$i+1]}"  
        echo "$pair" 
        # do what you want with the pair here
     fi  
done

Upvotes: 0

Related Questions