Lorsque j'exécute la command suivante:
#!/bin/bash while IFS= read -r -d '' file; do files+=$file done < <(find -type f -name '*.c' -print0) echo "${files[@]}"
Je n'ai pas le même résultat que celui-ci:
#!/bin/bash find_args="-type f '*.c' -print0" while IFS= read -r -d '' file; do files+=$file done < <(find $find_args) echo "${files[@]}"
Comment puis-je résoudre le deuxième scénario pour être équivalent au premier?
Ma compréhension est que, parce qu'il y a des guillemets simples dans les guillemets, les guillemets simples s'échappent, ce qui produit une expansion incorrecte qui ressemble à quelque chose comme ça:
find -type f -name ''\''*.c'\'' -print0
(Notez que vous avez une faute de frappe. Vous avez laissé l'indicateur -name
dans le deuxième exemple.)
Une approche consiste à mettre les arguments dans un tableau et à passer le tableau de façon appropriée pour find
…
#!/bin/bash find_args=(-type f -name '*.c' -print0) while IFS= read -r -d '' file; do files+=$file done < <(find "${find_args[@]}") echo "${files[@]}"
Le format ${foo[@]}
s'étend à tous les éléments du tableau, chacun étant un mot individuel (au lieu d'être étendu à une seule string). C'est plus proche dans l'intention du script original.
La réponse de Blayer est correcte, mais pour débuild ce qui se passe vraiment ici (en ignorant la faute du nom manquant -name
primaire):
#!/bin/bash while IFS= read -r -d '' file; do files+=$file done < <(find -type f -name '*.c' -print0) echo "${files[@]}"
Dans le shell lancé par la substitution de process ( <(...)
), la command suivante est analysée par bash:
find -type f -name '*.c' -print0
Parce que le glob *.c
est cité, bash ne le développe pas. Cependant, les guillemets simples sont supprimés. Ainsi, lorsque le process de find
commence, ce qu'il considère comme sa list d'arguments est:
-type f -name *.c -print0
Notez que ces arguments sont séparés par des octets NULL , et non par des espaces ou des returns à la ligne. C'est au niveau C, pas au niveau de la shell. Cela a à voir avec la façon dont les programmes sont exécutés en utilisant execve()
dans C.
Maintenant, pour contraster, dans l'extrait suivant:
#!/bin/bash find_args="-type f -name '*.c' -print0" while IFS= read -r -d '' file; do files+=$file done < <(find $find_args) echo "${files[@]}"
La valeur de la variable find_args
est définie sur:
-type f -name '*.c' -print0
(Les guillemets doubles ne font pas partie de la valeur, mais les guillemets simples le sont. )
Lorsque la command find $find_args
est exécutée, le jeton $find_args
est soumis à l'expansion des parameters, suivi de la division des mots, suivi de l' expansion du nom de domaine (aka glob expansion).
Après l'extension du paramètre, vous avez -type f -name '*.c' -print0
. Notez que c'est après la suppression de devis. Ainsi, les guillemets simples ne seront pas supprimés.
Après la division des mots, vous avez les mots suivants séparés:
-type f -name '*.c' -print0
Puis vient l'expansion de pathname. Bien sûr, '*.c'
ne correspondra probablement à rien car vous ne mettez généralement pas de guillemets simples dans vos noms de files, donc le résultat sera probablement que '*.c'
sera passé comme un model littéral à find
, et ainsi, le nom -name
primaire échouera sur tous les files. (Il ne réussirait que s'il y a un file dont le nom commence par un guillemet simple et se termine par les trois caractères .c'
)
Edit: En fait, s'il y a un tel file, le glob '*.c'
sera développé pour correspondre à ce file et à tous les autres files, puis l' extension [le nom réel du file] sera passé comme model. Donc, si le -print0
primary sera jamais atteint ou non, cela dépend de (a) s'il y a un seul nom de file, et (b) si ce nom de file, interprété comme un glob, correspond à lui-même.
Exemples:
Si vous exécutez, touch "'something.c'"
, alors le glob '*.c'
sera étendu à 'something.c'
, puis la find
primaire -name 'something.c'
correspondra également à ce file. être imprimé.
Si vous exécutez touch "'namewithcharset[a].c'"
, le glob '*.c'
sera étendu à celui par le shell, mais la find
-name 'namewithcharset[a].c'
ne sera pas identique, il ne correspondrait qu'à 'namewithcharseta.c'
, qui n'existe pas-donc -print0
ne serait pas atteint.
Si vous exécutez touch "'x.c'" "'y.c'"
, le glob '*.c'
sera étendu aux deux noms de files, ce qui provoquera une erreur à partir de find
car 'y.c'
n'est pas un valide primaire (et il ne peut pas être car il ne commence pas par un trait d'union).
Si l'option nullglob
est définie, vous obtiendrez un comportement différent.
Voir également:
En plus de ce qui a déjà été dit, vous devez:
$files
tant que tableau car par défaut scalar et var+=something
sur un scalaire fait une concaténation de string (ou une addition arithmétique si le scalaire a reçu l'atsortingbut entier ). Ou utilisez la syntaxe var+=(something)
(qui convertirait automatiquement la variable en tableau). Faire:
files=() while ... files+=$file # or files+=("$file") done
Cela suffirait à less que la variable de files
ait déjà été déclarée en tant que tableau associatif plus tôt dans le script (dans ce cas, les files+=something
ressemblerait à des files["0"]+=something
et des files+=("$files")
Erreur).
Si vous ne pouvez pas garantir que les files
n'ont pas été définis en tant que tableau associatif plus tôt dans le script, vous devrez peut-être:
typeset -a files=()
au contraire, cela aurait l'effet secondaire de limiter la scope de la variable à la fonction englobante. typeset -ga files=()
ne fonctionne pas correctement en tant que work around pour cela dans bash
car il déclarerait la variable dans la scope globale. unset files; files=()
unset files; files=()
peut ne pas fonctionner car les unset files
peuvent dans certains cas révéler la variable des files
d'une scope externe (qui peut être un tableau associatif) au lieu de l'annuler.
Vous devez faire quelques changements dans le deuxième script. Insérez le nom-manquant, supprimez les guillemets simples et utilisez set -f pour éviter l'expansion de l'astérisque. Soit au début du script ou (comme indiqué) dans le sous-shell:
<(set -f; find $find_args)
changez l'echo en printf et faites l'assignation en tant que files+=("$file")
pour avoir un tableau de valeurs. Ce que vous avez seulement construit une string.
#!/bin/bash find_args="-type f -name *.c -print0" while IFS= read -r -d '' file; do files+=("$file") done < <(set -f; find $find_args) printf '<%s> ' "${files[@]}"; echo