web-dev-qa-db-fra.com

Boucle sur des paires de valeurs dans bash

J'ai 10 fichiers texte et je veux paste chaque fichier avec sa paire, de sorte que j'ai 5 fichiers au total.

J'ai essayé ce qui suit:

for i in 4_1 5_1 6_1 7_1 8_1
do
for j in 4_2 5_2 6_2 7_2 8_2
do
paste ${i}.txt ${j}.txt > ${i}.${j}.txt
done
done

Cependant, ce code combine toutes les combinaisons possibles au lieu de simplement combiner les paires correspondantes.

Je voudrais donc que le fichier 4_1.txt soit associé à 4_2.txt, 5_1.txt à 5_2.txt, etc.

13
Rish

Si vous voulez utiliser une variable et effectuer une action avec elle, il vous suffit d'utiliser une boucle:

for file in 4 5 6 7 8
do
   paste "${file}_1" "${file}_2"
done

Cela fera

paste 4_1 4_2
paste 5_1 5_2
...
9
fedorqui

Je suis d'accord avec la réponse actuellement proposée par fedorqui dans le contexte de la question actuellement posée. Ce qui suit est donné uniquement pour donner des réponses plus générales.

Une approche plus générale (pour bash 4.0 ou plus récent) consiste à stocker vos paires dans un tableau associatif:

declare -A pairs=( [4_1]=4_2 [5_1]=5_2 [6_1]=6_2 [7_1]=7_2 [8_1]=8_2 )
for i in "${!pairs[@]}"; do
  j=${pairs[$i]}
  paste "$i.txt" "$j.txt" >"${i}.${j}.txt"
done

Une autre solution (compatible avec les anciennes versions de bash) consiste à utiliser plusieurs tableaux classiques:

is=( 4_1 5_1 6_1 7_1 8_1 )
js=( 4_2 5_2 6_2 7_2 8_2 )
for idx in "${!is[@]}"; do
  i=${is[$idx]}
  j=${js[$idx]}
  paste "$i.txt" "$j.txt" >"$i.$j.txt"
done
19
Charles Duffy

Vous pouvez utiliser un tableau associatif:

animals=(dog cat mouse)
declare -A size=(
  [dog]=big
  [cat]=medium
  [mouse]=small
)
declare -A sound=(
  [dog]=barks
  [cat]=purrs
  [mouse]=cheeps
)
for animal in "${animals[@]}"; do
  echo "$animal is ${size[$animal]} and it ${sound[$animal]}"
done

Cela vous permet de parcourir des paires, des triples, etc. Crédits: l'idée originale est tirée de la réponse de @ CharlesDuffy-s.

6
VasiliNovikov

Il existe un modèle courant dans lequel vous avez des paires de fichiers, où un nom de la paire peut facilement être dérivé de l'autre. Si le fichier dont vous connaissez le nom est X et que l'autre fichier est Y, vous avez les cas d'utilisation courants suivants.

  • Pour renommer, Y est X avec une extension supprimée et/ou un horodatage ajouté.
  • Pour le transcodage, Y est X avec une extension différente et peut-être un répertoire différent.
  • Pour de nombreuses tâches d'analyse de données, X et Y partagent certaines parties du nom de fichier, mais ont des paramètres ou des extensions différents.

Tous se prêtent au même squelette de code approximatif.

for x in path/to/base*.ext; do
    dir=${x%/*}   # Trim trailing file name, keep dir
    base=${x##*/} # Trim any leading directory

    # In this case, $y has a different subdirectory and a different extension
    y=${dir%/to}/from/${base%.ext}.newext

    # Maybe check if y exists?  Or doesn't exist?
    if [ -e "$y" ]; then
        echo "$0: $y already exists -- skipping" >&2
        continue
    fi

    mv or ffmpeg or awk or whatever "$x" and "$y"
done

La clé ici est l'observation que y peut être dérivé de x avec quelques substitutions de variables simples. Donc, vous passez en boucle sur les valeurs x et vous déterminez la valeur y correspondante à l'intérieur de la boucle.

Ici, nous avons utilisé les opérateurs ${variable#prefix} et ${variable%suffix} intégrés au shell pour renvoyer la valeur de la variable avec les variables prefix ou fin, respectivement suffix, coupées. (Il existe également ## et %% correspondant au plus long, au lieu du plus court, correspondance possible. L'expression après le # ou le % est un motif standard de shell global.) Celles-ci devraient généralement être tout ce dont vous avez besoin, même si vous voyez fréquemment des scripts sed ou awk même pour ce travail trivial (où vous devriez vraiment essayer d'éviter un processus externe), ainsi que pour des transformations plus exigeantes.

Si vous devez boucler des fichiers x dispersés dans différents répertoires, la boucle devrait peut-être commencer par quelque chose comme:

 find dir1 dir2 etc/and/so/forth -type f -name 'x-files*.ext' -print |
 while IFS='' read -r x; do
     :

Un problème fréquemment rencontré dans des questions similaires est celui des réponses qui ne permettent pas de citer correctement $x et $y. Généralement, toute variable contenant un nom de fichier doit toujours être entre guillemets.

Lorsque X et Y ne sont pas liés, une solution courante consiste à boucler un document ici contenant le mappage:

while read -r x y; do
    : stuff with "$x" and "$y"
done <<'____HERE'
    first_x_value  first_y_value
    another_x      corresponding_y
    random         surprise
____HERE
4
tripleee

ce qui précède n'a pas fonctionné pour moi, mais ce qui suit lit les valeurs par paires à partir d'une liste ordonnée. 

(peut être plus que des paires ajoutant des 'lignes de lecture' supplémentaires :-)

while read x; do
  read y
  echo $x $y
done << '___HERE'
X1
Y1
X2
Y2
X3
Y3
___HERE

produit

X1 Y1
X2 Y2
X3 Y3
1
splaisan