Velvet Star Monitor

Standout celebrity highlights with iconic style.

news

Select elements in list in groups of 2 - Bash

Writer Andrew Mclaughlin

I am trying to select elements in a list of files using a sequence. For this I have created a list of all my files following a pattern in a folder:

ls -d -1 /my/path/S1*.zip

Where the output is:

/my/path/S1A_IW_SLC__1SDV_20180412T171648_20180412T171715_021437_024E95_BDA1.zip
/my/path/S1A_IW_SLC__1SDV_20180424T171648_20180424T171715_021612_02540A_BB21.zip
/my/path/S1A_IW_SLC__1SDV_20180506T171649_20180506T171716_021787_025996_98AB.zip
/my/path/S1A_IW_SLC__1SDV_20180518T171649_20180518T171716_021962_025F27_A15C.zip
/my/path/S1A_IW_SLC__1SDV_20180530T171650_20180530T171717_022137_0264C8_5D94.zip
/my/path/S1A_IW_SLC__1SDV_20180611T171651_20180611T171718_022312_026A3D_BBFC.zip
/my/path/S1A_IW_SLC__1SDV_20180623T171652_20180623T171719_022487_026F7C_450E.zip
/my/path/S1A_IW_SLC__1SDV_20180623T171652_20180623T171719_543456_64324F_452W.zip

I would like to create a forloop in which for every iteration, the files are processed in groups of two. So, in the first iteration, file in position 1 in my list and file in position 2 are processed. In the second interation, file in position 3 and file in position 4, etc.

To make it simpler, I will take the example of unzipping these files in grousp of 2:

for i in $(ls -d -1 /shared/Training/WROC0320_UrbanMapping_Germany/Original/S1*.zip)
do
unzip i
unizp i+1 # Not sure if i+1 works like that in `bash`
done

The problem I see with this approach is that in the first iteration it will work (i will be 1 and 2) but in the secodn i will be 2 and 3 and it should be 3 and 4.

My question is then, how to iterate over the result of ls is such way?

2 Answers

In general, you should avoid iterating over the results of ls - see for example

Compared to a for loop, a while loop gives you much more control over delimiters and file selection. For example:

printf '%s\0' my/path/*.zip | while IFS= read -r -d '' f1 && IFS= read -r -d '' f2; do echo 'unzipping files'; echo unzip "$f1"; echo unzip "$f2"
done
unzipping files
unzip my/path/S1A_IW_SLC__1SDV_20180412T171648_20180412T171715_021437_024E95_BDA1.zip
unzip my/path/S1A_IW_SLC__1SDV_20180424T171648_20180424T171715_021612_02540A_BB21.zip
unzipping files
unzip my/path/S1A_IW_SLC__1SDV_20180506T171649_20180506T171716_021787_025996_98AB.zip
unzip my/path/S1A_IW_SLC__1SDV_20180518T171649_20180518T171716_021962_025F27_A15C.zip
unzipping files
unzip my/path/S1A_IW_SLC__1SDV_20180530T171650_20180530T171717_022137_0264C8_5D94.zip
unzip my/path/S1A_IW_SLC__1SDV_20180611T171651_20180611T171718_022312_026A3D_BBFC.zip
unzipping files
unzip my/path/S1A_IW_SLC__1SDV_20180623T171652_20180623T171719_022487_026F7C_450E.zip
unzip my/path/S1A_IW_SLC__1SDV_20180623T171652_20180623T171719_543456_64324F_452W.zip

Here printf '%s\0' my/path/S*.zip expands the shell glob into a list that is unambiguously delimited by the NULL character (the only character that may not appear in a Unix filepath), and then each read command reads one of them. The IFS= unsets the field separator - the only effect of this is to prevent the reads from stripping leading whitespace (in the very unlikely event that you need to handle such names).


A possibly cleaner way, using the set builtin with a shell glob to read the file names unambiguously into the positional parameter array $@:

shopt -s nullglob
set -- my/path/S*.zip
while (($# > 1)); do echo "unzipping files"; echo unzip "$1"; echo unzip "$2"; shift 2
done
3

Loop a version sorted array with step of two.

#!/bin/bash
eval \
"a=($(ls -v1 --quoting-style=shell-escape files/*))"
for ((i=0; i<${#a[@]}; i+=2)); do [[ -r ${a[$i]} ]] && \ unzip "${a[$i]}" [[ -r ${a[(i+1)]} ]] && \ unzip "${a[(i+1)]}"
done
2

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy