Étant donné ces noms de fichiers:
$ ls -1
file
file name
otherfile
bash
lui-même fait parfaitement bien avec les espaces blancs intégrés:
$ for file in *; do echo "$file"; done
file
file name
otherfile
$ select file in *; do echo "$file"; done
1) file
2) file name
3) otherfile
#?
Cependant, parfois, je ne souhaiterais peut-être pas travailler avec tous les fichiers, ou même strictement dans $PWD
, c'est là find
qu'intervient. Qui gère également les espaces nominalement:
$ find -type f -name file\*
./file
./file name
./directory/file
./directory/file name
J'essaie de concocter une version sécurisée de ce scriptlet qui prendra la sortie de find
et la présentera dans select
:
$ select file in $(find -type f -name file); do echo $file; break; done
1) ./file
2) ./directory/file
Cependant, cela explose avec des espaces dans les noms de fichiers:
$ select file in $(find -type f -name file\*); do echo $file; break; done
1) ./file 3) name 5) ./directory/file
2) ./file 4) ./directory/file 6) name
Habituellement, je contournerais cela en jouant avec IFS
. Pourtant:
$ IFS=$'\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'
$ IFS='\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'
Quelle est la solution pour ceci?
bash
text-processing
whitespace
select
DopeGhoti
la source
la source
find
pour sa capacité à correspondre à un nom de fichier particulier, vous pouvez simplement utiliserselect file in **/file*
(après le réglageshopt -s globstar
) enbash
4 ou version ultérieure.Réponses:
Si vous avez seulement besoin de gérer les espaces et les tabulations (pas les sauts de ligne incorporés), vous pouvez utiliser
mapfile
(ou son synonymereadarray
) pour lire dans un tableau, par exemple donnéensuite
Si vous avez besoin de gérer les sauts de ligne, et votre
bash
version offre une valeur NULL délimité parmapfile
1 , vous pouvez alors modifier ce àIFS= mapfile -t -d '' files < <(find . -type f -print0)
. Sinon, assemblez un tableau équivalent à partir d'unefind
sortie délimitée par des null à l' aide d'uneread
boucle:1 l'
-d
option a été ajoutée àmapfile
dans labash
version 4.4 IIRCla source
mapfile
c'est nouveau pour moi aussi. Gloire.while IFS= read
version fonctionne en bash v3 (ce qui est important pour ceux d'entre nous qui utilisent macOS).find -print0
variante; grogner pour l'avoir mis après une version incorrecte connue, et le décrire uniquement pour une utilisation si l'on sait qu'ils doivent gérer les retours à la ligne. Si l'on ne gère l'inattendu qu'aux endroits où il est attendu, on ne le manipulera jamais du tout.Cette réponse propose des solutions pour tout type de fichiers. Avec des nouvelles lignes ou des espaces.
Il existe des solutions pour les bash récents ainsi que les anciens bash et même les anciens obus posix.
L'arbre ci-dessous dans cette réponse [1] est utilisé pour les tests.
sélectionner
Il est facile de
select
travailler soit avec un tableau:Ou avec les paramètres positionnels:
Ainsi, le seul vrai problème est d'obtenir la "liste des fichiers" (correctement délimitée) à l'intérieur d'un tableau ou à l'intérieur des paramètres positionnels. Continue de lire.
frapper
Je ne vois pas le problème que vous signalez avec bash. Bash est capable de rechercher dans un répertoire donné:
Ou, si vous aimez une boucle:
Notez que la syntaxe ci-dessus fonctionnera correctement avec n'importe quel shell (raisonnable) (pas csh au moins).
La seule limite de la syntaxe ci-dessus est de descendre dans d'autres répertoires.
Mais bash pourrait faire ça:
Pour sélectionner uniquement certains fichiers (comme ceux qui se terminent par un fichier), remplacez simplement le *:
robuste
Lorsque vous placez un "espace sûr " dans le titre, je vais supposer que ce que vous vouliez dire était " robuste ".
Le moyen le plus simple d'être robuste sur les espaces (ou les retours à la ligne) est de rejeter le traitement des entrées qui ont des espaces (ou des retours à la ligne). Un moyen très simple de le faire dans le shell est de quitter avec une erreur si un nom de fichier se développe avec un espace. Il y a plusieurs façons de le faire, mais le plus compact (et posix) (mais limité à un contenu de répertoire, y compris les noms suddirectories et en évitant les fichiers dot) est:
Si la solution utilisée est robuste dans l'un de ces éléments, supprimez le test.
En bash, les sous-répertoires pouvaient être testés à la fois avec le ** expliqué ci-dessus.
Il existe plusieurs façons d'inclure des fichiers dot, la solution Posix est la suivante:
trouver
Si find doit être utilisé pour une raison quelconque, remplacez le délimiteur par un NUL (0x00).
bash 4.4+
bash 2.05+
POSIXLY
Pour créer une solution POSIX valide où find n'a pas de délimiteur NUL et où il n'y a pas
-d
(ni-a
) de lecture, nous avons besoin d'une approche entièrement différente.Nous devons utiliser un complexe
-exec
de find avec un appel à un shell:Ou, si ce qui est nécessaire est une sélection (la sélection fait partie de bash, pas sh):
[1] Cet arbre (les \ 012 sont des sauts de ligne):
Pourrait être construit avec ces deux commandes:
la source
Vous ne pouvez pas définir une variable devant une construction en boucle, mais vous pouvez la définir devant la condition. Voici le segment de la page de manuel:
(Une boucle n'est pas une simple commande .)
Voici une construction couramment utilisée illustrant les scénarios d'échec et de réussite:
Malheureusement, je ne vois pas de moyen d'incorporer une modification
IFS
dans laselect
construction tout en ayant une incidence sur le traitement d'un associé$(...)
. Cependant, rien n'empêche d'IFS
être placé en dehors de la boucle:et c'est cette construction avec laquelle je peux voir fonctionner
select
:Lors de l'écriture de code défensif, je recommanderais que la clause soit exécutée dans un sous-shell, et
IFS
etSHELLOPTS
enregistrée et restaurée autour du bloc:la source
IFS=$'\n'
est sûr n'est pas fondé. Les noms de fichiers sont parfaitement capables de contenir des littéraux de nouvelle ligne.[0-9a-f]{24}
. Des To de sauvegardes de données utilisées pour prendre en charge la facturation client ont été perdus.select
par sa conception même est pour les solutions scriptées , il doit donc toujours être conçu pour gérer les cas de bord.select
partir d'un shell où vous tapez les commandes à exécuter, mais uniquement à partir d'un script, où vous répondez à une invite fournie par ce script , et où se trouve ce script exécuter une logique prédéfinie (construite à l'insu des noms de fichiers utilisés) sur la base de cette entrée.Je suis peut-être hors de ma juridiction ici, mais vous pouvez peut-être commencer par quelque chose comme ça, au moins cela n'a aucun problème avec les espaces blancs:
Pour éviter toute fausse hypothèse potentielle, comme indiqué dans les commentaires, sachez que le code ci-dessus est équivalent à:
la source
read -d
est une solution intelligente; Merci pour cela.read -d $'\000'
est exactement identique àread -d ''
, mais pour tromper les gens sur les capacités de bash (impliquant, à tort, qu'il est capable de représenter des NULs littéraux dans des chaînes). Exécutezs1=$'foo\000bar'; s2='foo'
, puis essayez de trouver un moyen de distinguer les deux valeurs. (Une future version pourrait se normaliser avec un comportement de substitution de commande en rendant la valeur stockée équivalente àfoobar
, mais ce n'est pas le cas aujourd'hui).