web-dev-qa-db-fra.com

Comment recueillir correctement un tableau de lignes dans ZSH

Je pensais que ce qui suit regrouperait la production de my_command Dans un tableau de lignes:

IFS='\n' array_of_lines=$(my_command);

pour que $array_of_lines[1] ferait référence à la première ligne dans la sortie de my_command, $array_of_lines[2] au second, et ainsi de suite.

Cependant, la commande ci-dessus ne semble pas bien fonctionner. Il semble également diviser la sortie de my_command autour du personnage n, comme je l'ai vérifié avec print -l $array_of_lines, que je crois imprime des éléments d'une ligne de tableau par ligne. J'ai aussi vérifié cela avec:

echo $array_of_lines[1]
echo $array_of_lines[2]
...

Dans une deuxième tentative, j'ai pensé que l'ajout eval pourrait aider:

IFS='\n' array_of_lines=$(eval my_command);

mais j'ai exactement le même résultat que sans cela.

Enfin, en suivant la réponse sur Liste des éléments avec des espaces dans ZSH , j'ai également essayé d'utiliser des drapeaux d'expansion des paramètres au lieu de IFS pour dire à ZSH comment diviser l'entrée et collecter les éléments en un Array, c'est-à-dire:

array_of_lines=("${(@f)$(my_command)}");

Mais j'ai toujours le même résultat (se fractionnement se produisant sur n)

Avec cela, j'ai les questions suivantes:

Q1. Qu'est-ce que "Les moyens" de collecte de la sortie de une commande dans un tableau de lignes?

Q2. Comment puis-je spécifier IFS à scinder sur les nouvelles lignes seulement?

Q3. Si j'utilise des drapeaux d'expansion des paramètres comme dans ma troisième tentative ci-dessus (c'est-à-dire à l'aide de @f) Pour spécifier le fractionnement, ZSH ignore-t-il la valeur de IFS? Pourquoi n'a-t-il pas travaillé ci-dessus?

44

Tl, dr:

array_of_lines=("${(@f)$(my_command)}")

Première erreur (→ Q2): IFS='\n' Définit IFS sur les deux caractères \ Et n. Pour définir IFS à une nouvelle ligne, utilisez IFS=$'\n'.

Deuxième erreur: pour définir une variable à une valeur de matrice, vous avez besoin de parenthèses autour des éléments: array_of_lines=(foo bar).

Cela fonctionnerait, sauf qu'il lande des lignes vides, car les espaces consécutifs comptent comme un seul séparateur:

IFS=$'\n' array_of_lines=($(my_command))

Vous pouvez conserver les lignes vides sauf à la fin en doublant le caractère de l'espace de blancheur dans IFS:

IFS=$'\n\n' array_of_lines=($(my_command))

Pour continuer à traîner les lignes vides également, vous devez ajouter quelque chose à la sortie de la commande, car cela se produit dans la substitution de commande elle-même, pas de l'analyser.

IFS=$'\n\n' array_of_lines=($(my_command; echo .)); unset 'array_of_lines[-1]'

(En supposant que la sortie de my_command ne se termine pas dans une ligne non délimitée; Notez également que vous perdez l'état de sortie de my_command)

Notez que tous les extraits ci-dessus laissent IFS avec sa valeur non par défaut, ils peuvent donc gâcher le code suivant. Pour conserver le réglage de IFS local, mettez la chose entière dans une fonction où vous déclarez IFS local (ici aussi en tenant compte de la préservation du statut de sortie de la commande):

collect_lines() {
  local IFS=$'\n\n' ret
  array_of_lines=($("$@"; ret=$?; echo .; exit $ret))
  ret=$?
  unset 'array_of_lines[-1]'
  return $ret
}
collect_lines my_command

Mais je recommande de ne pas gâcher avec IFS; Au lieu de cela, utilisez le drapeau d'expansion f à scinder sur les nouvelles lignes (→ Q1):

array_of_lines=("${(@f)$(my_command)}")

Ou pour préserver les lignes vides de fuite:

array_of_lines=("${(@f)$(my_command; echo .)}")
unset 'array_of_lines[-1]'

La valeur de IFS n'a pas d'importance là-bas. Je soupçonne que vous avez utilisé une commande qui se divise sur IFS pour imprimer $array_of_lines Dans vos tests (→ Q3).

Deux numéros: premièrement, les citations doubles apparemment ne pas interpréter pas les escapades au dos (désolé de cela :). Utilisation $'...' devis. Et selon man zshparam, pour collecter des mots dans un tableau, vous devez les enfermer entre parenthèses. Donc cela fonctionne:

% touch 'a b' c d 'e f'
% IFS=$'\n' arr=($(ls)); print -l $arr
a b
c
d
e f
% print $arr[1]
a b

Je ne peux pas répondre à votre Q3. J'espère que je n'aurai jamais besoin de connaître de telles choses ésotériques :).

5
angus

Vous pouvez également utiliser TR pour remplacer la nouvelle ligne avec de l'espace:

lines=($(mycommand | tr '\n' ' '))
select line in lines; do
  echo $line
  break
done
0
Jimchao