web-dev-qa-db-fra.com

ssh sort de la boucle while en bash

J'utilise ce code bash pour télécharger des fichiers sur un serveur distant, pour les fichiers normaux, cela fonctionne bien:

for i in `find devel/ -newer $UPLOAD_FILE`
do
    echo "Upload:" $i
    if [ -d $i ]
    then
        echo "Creating directory" $i
        ssh $USER@$SERVER "cd ${REMOTE_PATH}; mkdir -p $i"
        continue
    fi
    if scp -Cp $i $USER@$SERVER:$REMOTE_PATH/$i
    then
        echo "$i OK"
    else
        echo "$i NOK"
        rm ${UPLOAD_FILE}_tmp
    fi
done

Le seul problème est que pour les fichiers avec un espace dans le nom, la boucle for échoue, j'ai donc remplacé la première ligne comme ceci:

find devel/ -newer $UPLOAD_FILE | while read i
do
    echo "Upload:" $i
    if [ -d $i ]
    then
        echo "Creating directory" $i
        ssh $USER@$SERVER "cd ${REMOTE_PATH}; mkdir -p $i"
        continue
    fi
    if scp -Cp $i $USER@$SERVER:$REMOTE_PATH/$i
    then
        echo "$i OK"
    else
        echo "$i NOK"
        rm ${UPLOAD_FILE}_tmp
    fi
done

Pour une raison étrange, la commande ssh sort de la boucle while, donc le premier répertoire manquant est bien créé, mais tous les fichiers/répertoires manquants suivants sont ignorés.

Je suppose que cela a quelque chose à voir avec l'écriture de ssh quelque chose sur stdout qui confond la commande "read". La mise en commentaire de la commande ssh fait fonctionner la boucle comme il se doit.

Est-ce que quelqu'un sait pourquoi cela se produit et comment peut-on empêcher ssh de rompre la boucle while?

83
Robby75

Le problème est que ssh lit à partir de l'entrée standard, donc il mange toutes vos lignes restantes. Vous pouvez simplement connecter son entrée standard à nulle part:

ssh $USER@$SERVER "cd ${REMOTE_PATH}; mkdir -p $i" < /dev/null

Vous pouvez aussi utiliser ssh -n au lieu de la redirection.

192
choroba

Une autre approche consiste à boucler sur un FD autre que stdin:

while IFS= read -u 3 -r -d '' filename; do
  if [[ -d $filename ]]; then
    printf -v cmd_str 'cd %q; mkdir -p %q' "$REMOTE_PATH" "$filename"
    ssh "$USER@$SERVER" "$cmd_str"
  else
    printf -v remote_path_str '%q@%q:%q/%q' "$USER" "$SERVER" "$REMOTE_PATH" "$filename"
    scp -Cp "$filename" "$remote_path_str"
  fi
done 3< <(find devel/ -newer "$UPLOAD_FILE" -print0)

Les opérateurs -u 3 Et 3< Sont critiques ici, en utilisant FD 3 plutôt que FD 0 par défaut (stdin).

L'approche donnée ici - en utilisant -print0, Une valeur IFS effacée, etc. - est également moins boguée que le code original et la réponse existante, qui ne peut pas gérer correctement les noms de fichiers intéressants . (La réponse de Glenn Jackman est proche, mais même cela ne peut pas traiter les noms de fichiers avec des nouvelles lignes ou les noms de fichiers avec des espaces en fin).

L'utilisation de printf %q Est essentielle pour générer des commandes qui ne peuvent pas être utilisées pour attaquer la machine distante. Considérez ce qui se passerait avec un fichier nommé devel/$(rm -rf /)/hello avec du code qui ne présentait pas cette paranoïa.

11
Charles Duffy