1

1) Declaration

I have pairs of, for example, directories. The pairs contain spaces (and/or other inconveniences like extra periods or special chars. Anything ext4 easily supports let say.)

'/foo/dolor sit/amet.elit'         '/bar/accumsan/luctus massa.nec'
'/baz/sagittis. Fusce/vitae.dolor' '/bam/Nunc aliquetjusto.metus'

I want to write a bash script where these pairs are hard-coded/constants.

I would highly prefer declaring the strings within a delimiter or such that does not need the arbitrary string to be preprocessed (eg I don't want to add escapes on all literal whitespaces if possible)


2) Iteration

With declared pairs of strings, I wish to enter a loop where both the strings are available in one iteration as separate variables, e.g. $LEFT and $RIGHT.

Likely uses being simple file operations like copy, move, link, diff.


Notes:

  • The "height" of the array should allow many many pairs.
  • If the declaration and iteration can support wider-than-2 2D arrays please note how to extend to desired with.
  • If bash has an "array" specific datatype, its use is not mandatory to the question/answer. Any iterable 2-dimensional declaration and iteration is sufficient.
ThorSummoner
  • 16,657
  • 15
  • 135
  • 147

5 Answers5

8

A stupid-simple approach, If you're going to hard code the data anyway, you can hard code the data as parameters to shell functions.

#!/usr/bin/env bash

do_for_pair() {
    echo "$1"
    echo "$2"
}

do_for_pair "/foo/dolor sit/amet.elit"         "/bar/accumsan/luctus massa.nec"
do_for_pair "/baz/sagittis. Fusce/vitae.dolor" "/bam/Nunc aliquetjusto.metus"
ThorSummoner
  • 16,657
  • 15
  • 135
  • 147
5

You might use an associative array (bash 4 or later):

declare -A arr
arr['/foo/dolor sit/amet.elit']='/bar/accumsan/luctus massa.nec'
arr['/baz/sagittis. Fusce/vitae.dolor']='/bam/Nunc aliquetjusto.metus'

for LEFT in "${!arr[@]}"; do
    RIGHT=${arr["$LEFT"]}
    ...
done
chepner
  • 497,756
  • 71
  • 530
  • 681
  • There is no reason to hesitate putting messy values in the array key field? Also bash will not reorder array elements by key name will it? – ThorSummoner Sep 30 '15 at 18:30
  • 1
    They key can be any string you can properly quote. The order of the keys is--unfortunately, if that matters--arbitrary. – chepner Sep 30 '15 at 18:32
  • Arbitrary ordering is desirable in my case, I can alphabetize them myself if I wanted that. Gotta ask; I don't know what to expect from arrays in bash, I expect them to be pretty cumbersome as all of bash login and logic docs seem to be. – ThorSummoner Sep 30 '15 at 18:39
4

One way to do this in Bash is to put the pairs one after the other in a normal linear array and then step through it 2 entries at a time:

filepairs=(
    '/foo/dolor sit/amet.elit'         '/bar/accumsan/luctus massa.nec'
    '/baz/sagittis. Fusce/vitae.dolor' '/bam/Nunc aliquetjusto.metus'
)

for (( idx=0 ; idx<${#filepairs[@]} ; idx+=2 )) ; do
    LEFT=${filepairs[idx]}
    RIGHT=${filepairs[idx+1]}

    echo "LEFT=$LEFT"
    echo "RIGHT=$RIGHT"
    echo
done

The same technique can be used to handle wider 2D arrays. For instance, to handle triples just put them in the array one after another and step through the array 3 at a time.

pjh
  • 6,388
  • 2
  • 16
  • 17
  • This was the only way I could really imagine solving this problem in bash, I hesitate to use this as I fear user error on input is far more likely than any other solution. – ThorSummoner Sep 30 '15 at 18:33
2

I would suggest using gnu-awk with FPAT to split this file like this:

awk -v FPAT="'[^']+'" '{system("echo cp \"" $1 "\" \"" $2 "\"")}' file

Output:

cp '/foo/dolor sit/amet.elit' '/bar/accumsan/luctus massa.nec'
cp '/baz/sagittis. Fusce/vitae.dolor' '/bam/Nunc aliquetjusto.metus'

Once you verify the output you can remove echo from system function call.

anubhava
  • 761,203
  • 64
  • 569
  • 643
  • I don't clearly understand how do do multiple shell actions (which sorry I didn't clarify I wanted) with feild 1 and feild 2 in your example. One of the goals of this question (which again I didn't clarify) would be to do a copy/move/link/diff operation with the LEFT and RIGHT components (as paths). – ThorSummoner Sep 30 '15 at 16:54
  • 1
    I suggest editing the questin with desired action you want to take on `LEFT` and `RIGHT` parts so that I can also improve my answer. – anubhava Sep 30 '15 at 16:59
  • I thought you'd suggested some edits, now I understand and have edited the text of the Iteration section to request both LEFT and RIGHT avaiable in a single iteration. – ThorSummoner Sep 30 '15 at 17:20
1

I would put the list of pairs in a here doc, as this:

: <<-'_pair_values_list_'
'/foo/dolor sit/amet.elit' '/bar/accumsan/luctus massa.nec'
'/baz/sagittis. Fusce/vitae.dolor' '/bam/Nunc aliquetjusto.metus'
_pair_values_list_

And then, assuming the single quote ' is not part of the values to be used in pairs, read them:

#!/bin/bash
i=0
while IFS=\' read t LEFT t RIGHT t; do
    printf 'line count is: %4d ' "$(( ++i ))"
    printf 'with LEFT=%-35s' "$LEFT"
    printf 'with RIGHT=%-35s\n' "$RIGHT"
    done <<-'_pair_values_list_'
'/foo/dolor sit/amet.elit' '/bar/accumsan/luctus massa.nec'
'/baz/sagittis. Fusce/vitae.dolor' '/bam/Nunc aliquetjusto.metus'
_pair_values_list_

The t are just a throwaway variable.

The output will be:

line count is:    1 with LEFT=/foo/dolor sit/amet.elit           with RIGHT=/bar/accumsan/luctus massa.nec
line count is:    2 with LEFT=/baz/sagittis. Fusce/vitae.dolor   with RIGHT=/bam/Nunc aliquetjusto.metus 

The here doc <<.... could be changed to a file <file.pairs later on if the number of pairs increases too much (more than some hundreds).

And the variables read (LEFT RIGTH) could be assigned to some array if more complex processing is needed in the future. I am assuming that is not needed now.

Note that this script will not be able to deal with filenames that contain newlines.