Erreur de script Bash avec des chaînes avec des chemins contenant des espaces et des caractères génériques

Jai du mal à comprendre les bases du script Bash. Voici ce que jai jusquà présent:

#!/bin/bash FILES="/home/john/my directory/*.txt" for f in "${FILES}" do echo "${f}" done 

Tout ce que je veux faire est de lister tous les fichiers .txt dans une boucle for pour que je puisse faire des choses avec eux. Mais lespace dans my directory et lastérisque dans *.txt ne joue tout simplement pas bien. Jai essayé de lutiliser avec et sans guillemets doubles, avec et sans accolades sur les noms de variables et je ne peux toujours pas « t imprimer tous les fichiers .txt.

Ceci est un chose très basique, mais je me bats encore parce que je suis fatigué et je ne peux pas penser correctement.

Quest-ce que je fais de mal?

Jai réussi à postuler le script ci-dessus si mes FICHIERS nont pas despace ou dastérisque … Jai dû expérimenter avec ou sans lutilisation de guillemets doubles et daccolades pour le faire fonctionner. Mais au moment où jai les deux espaces et un astérisque, ça gâche tout.

Answer

Entre guillemets, le * ne se développera pas en une liste de fichiers. Pour utiliser un tel caractère générique avec succès, il doit être en dehors des guillemets.

Même si le caractère générique sest développé, lexpression "${FILES}" donnerait une seule chaîne, pas une liste de fichiers.

Une approche qui fonctionnerait serait:

#!/bin/bash DIR="/home/john/my directory/" for f in "$DIR"/*.txt do echo "${f}" done 

Dans ce qui précède, les noms de fichiers avec des espaces ou autres difficiles les caractères seront gérés correctement.

Une approche plus avancée pourrait utiliser des tableaux bash:

#!/bin/bash FILES=("/home/john/my directory/"*.txt) for f in "${FILES[@]}" do echo "${f}" done 

Dans ce cas, FILES est un tableau de noms de fichiers. Les parenthèses entourant la définition en font un tableau. Notez que * est en dehors des guillemets. La construction "${FILES[@]}" est un cas particulier: elle se développera en une liste de chaînes où chaque chaîne est lun des noms de fichier. Les noms de fichiers avec des espaces ou dautres caractères difficiles seront traités correctement.

Commentaires

  • super qui a fonctionné
  • Ça ‘ est intéressant de noter que si vous ‘ passez des chemins comme celui-ci autour des fonctions, vous devez vous assurer de citer la variable seule plutôt que de le concaténer dans une chaîne plus grande: for f in "$DIR"/*.txt = fine for f in "$DIR/*.txt" = breaks

Réponse

Bien que lutilisation de tableaux comme le montre John1024 ait beaucoup plus de sens, ici, vous pouvez également utiliser lopérateur split + glob (en laissant une variable scalaire sans guillemets).

Puisque vous ne voulez que la partie glob de cet opérateur, vous devez désactiver la partie split :

#! /bin/sh - # that also works in any sh, so you don"t even need to have or use bash file_pattern="/home/john/my directory/*.txt" # all uppercase variables should be reserved for environment variables IFS="" # disable splitting for f in $file_pattern # here we"re not quoting the variable so # we"re invoking the split+glob operator. do printf "%s\n" "$f" # avoid the non-reliable, non-portable "echo" done 

Réponse

Ce que vous pouvez faire est de ne laisser que les caractères génériques en dehors des guillemets.
Quelque chose comme:
pour un in « fichiers avec des espaces » * « . txt « 
faire un traitement
fait
Si les caractères génériques se développent eux-mêmes en espaces, alors vous devrez utiliser une approche fichier par ligne, comme utiliser ls -l pour générer la liste des fichiers et utilisez bash read pour obtenir chaque fichier.

Réponse

Solution basée sur une seule ligne, (à exécuter dans le terminal):
/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; }; done; unset f;

pour votre cas / OP « , changez le "./" en "/home/john/my directory/"

À utiliser dans un fichier de script:

 #!/bin/bash /usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0 | while IFS= read -r -d $"\0" f ; do { echo "${f}"; # your other commands/codes, etc }; done; unset f;  

La fonctionnalité ci-dessus peut également être obtenue de cette manière (recommandée):

 #!/bin/bash while IFS= read -r -d $"\0" f ; do { echo "${f}"; # your other commands/codes, etc }; done < <(/usr/bin/find "./" -not -type d -maxdepth 1 -iname "*.txt" -print0); unset f;  

Brève / Brève DESCRIPTION:

"./": cest le répertoire courant. spécifiez un chemin de répertoire.
-not -type d: ici le -not le configure pour ignorer le type mentionné suivant, & mentionné suivant -type est d = répertoires, donc il sautera répertoires. Utilisez f au lieu de d pour ignorer les fichiers. Utilisez l au lieu de d pour ignorer les fichiers de lien symbolique.
-maxdepth 1: est en train de le configurer pour trouver les fichiers au niveau du répertoire courant (aka: un) uniquement. Pour trouver un fichier à lintérieur de chaque & premier niveau de sous-répertoire, définissez maxdepth sur 2. Si -maxdepth nest pas utilisé, alors il effectuera une recherche récursive (dans les sous-répertoires), etc.
-iname "*.jpg": ici le -iname le configure pour rechercher des fichiers et les ignorer (supérieur / inférieur) -case dans le nom de fichier / lextension. Le -name nignore pas la casse. -lname trouve des liens symboliques. etc.
-print0: il imprime le chemin du fichier courant vers la sortie standard, suivi dun caractère ASCII NUL (code de caractère 0), que nous allons détection ultérieure en utilisant read dans while.
IFS=: ici il est utilisé dans le cas où un nom de fichier se termine par un espace. Nous utilisons NUL / "" / \0 avec IFS pour détecter chaque nom de fichier trouvé. Comme  » find  » est configuré pour les séparer par \0 qui est produit par -print0.
read -r -d $"\0" fileName: le $"\0" est "" / NUL. -r a été utilisé dans le cas où un nom de fichier a une barre oblique inverse.
read [-ers] [-a aname] [-d delim] [-i texte] [-n nchars] [-N nchars] [-p invite] [-t timeout] [-u fd] [nom. ..] […]
-r La barre oblique inverse nagit pas comme un caractère déchappement. La barre oblique inverse est considérée comme faisant partie de la ligne. En particulier, une paire anti-slash-nouvelle ligne ne peut pas être utilisée comme continuation de ligne.
done < <(...): Process-Substitution utilisé ici pour envoyer / canal de sortie de  » rechercher  » dans le  » lire  » sur  » tandis que  » -loop. Plus dinformations: https://www.gnu.org/software/bash/manual/html_node/Process-Substitution.html, https://wiki.bash-hackers.org/syntax/expansion/proc_subst, https://tldp.org/LDP/abs/html/abs-guide.html#PROCESS-SUB, https://tldp.org/LDP/abs/html/abs-guide.html#PSUBP


Dans une autre réponse de @ John1024, il a montré une excellente solution basée sur bash, qui nutilise pas le  » find « , un utilitaire externe.
 » find  » est très efficace & rapide, je le préfère quand il y a trop de fichiers à gérer.

dans la solution de @ John1024, il affichera le ligne de règle de correspondance lorsquil ny a pas de fichier dans le répertoire, donc la ligne ci-dessous [ ! -e "${f}" ]... est utilisée pour ignorer cela,
voici une solution sur une seule ligne à utiliser directement dans Terminal:
DIR="/home/john/my directory/" ; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; }; done; unset DIR;

Voici un script:

 #!/bin/bash DIR="/home/john/my directory/"; for f in "$DIR"*.txt ; do { [ ! -e "${f}" ] && continue; echo "${f}"; # your codes/commands, etc }; done; unset DIR;  

Remarque: si le répertoire dans DIR a "/" barre oblique (indicateur de répertoire) à la fin, alors dans la règle de correspondance , à nouveau utiliser le "/" nest pas nécessaire,
Ou, faites linverse: dans DIR, nutilisez pas le "/" à la fin et utilisez-le donc dans la règle de correspondance "$DIR"/*.txt


Cette vérification supplémentaire en utilisant [ ! -e "${f}" ]... peut être évité sil est inférieur à shell-option (alias:  » shopt « ) est utilisé ou activé:
shopt -s nullglob

Si un état shopt a été modifié par un script, alors dans un autre programme de script basé sur bash, cela crée des problèmes inattendus / imprévus.

Pour avoir un comportement cohérent dans tous les scripts qui utilisent bash, létat de loption bash-shell doit être enregistré / sauvegardé dans votre script, et une fois que vous avez fini dutiliser vos fonctions principales dans votre script, cette option shell doit être réinitialisée aux paramètres précédents.

Nous utilisons le backtick (aka: grave-accent, aka: backquote) `...` substitution de commande (pour les codes de commande bash internes, etc.), pour ne pas engendrer un nouveau sous-shell, conserver la signification littérale de la barre oblique inverse, pour une prise en charge plus large (alias: portabilité), etc., car les commandes bash internes basées sur un backtick, etc. peuvent souvent être exécutées dans le même shell que le script, etc., et cest donc un peu plus rapide = « c4609ee59c »>

mieux, et aussi mieux pour le but que nous traitons ici. Si vous préférez$(...)la substitution de commandes, utilisez-la, nimporte qui a la liberté & de choisir ce quil préfère, déviter, etc. Plus dinformations : ici .

Le script ci-dessus est à nouveau affiché, & cette fois avec les paramètres précédents dun shopt restauré, avant de terminer le script:

 #!/bin/bash DIR="/home/john/my directory/"; ub="/usr/bin"; # shopt = shell-option(s). # Setting-up "previous-nullglob" state to "enabled"/"on"/"1": p_nullglob=1; # The "shopt -s" command output shows list of enabled shopt list, so if # nullglob is NOT set to ON/enabled, then setting "previous_nullglob" as 0 [ "`shopt -s | ${ub}/grep nullglob`" == "" ] && p_nullglob=0; # Enabling shell-options "nullglob": shopt -s nullglob; # previous code, but without the extra checking [ ! -e "${f}" ]... line: for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; # As we have utilized enabled nullglob shopt, its now in enabled state, # so if previously it was disabled only-then we will disable it: [ "$p_nullglob" -eq "0" ] && shopt -u nullglob; unset DIR; unset p_nullglob ub;  

Sortie de shopt -p shoptName (par exemple: shopt -p dotglob) peut être
soit, ceci: shopt -u shoptName (le u est unset / disabled / off / 0)
ou, ceci: shopt -s shoptName ( s est set / enabled / on / 1)
La position de la lettre "s" ou "u" est toujours à 7 (car, en bash, un la position de la lettre de la chaîne « s commence à partir de 0, cest-à-dire que la 1ère lettre dune chaîne est à la position 0)
Nous peut obtenir ce "u" ou et le stocker dans une variable, afin que nous puissions lutiliser pour restaurer létat précédent.
Et si nous appliquons cette méthode (mentionnée ci-dessus) pour enregistrer / restaurer létat shopt, alors nous pouvons évitez dutiliser loutil externe "grep".

Pour afficher "txt" le fichier commençant par ".", cest-à-dire que pour afficher le fichier "txt" caché, nous devons activer "dotglob" shopt.

Donc cette fois ci-dessous, "dotglob" est inclus & activé pour afficher les fichiers HIDDEN "txt":

 #!/bin/bash DIR="/home/john/my directory/"; p_nullglob="u"; pSS="`shopt -p nullglob`"; [ "${pSS:7:1}" == "s" ] && p_nullglob="s"; p_dotglob="u"; pSS="`shopt -p dotglob`"; [ "${pSS:7:1}" == "s" ] && p_dotglob="s"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; [ "$p_nullglob" == "u" ] && shopt -u nullglob; [ "$p_dotglob" == "u" ] && shopt -u dotglob; unset DIR; unset p_nullglob p_dotglob pSS;  

Il existe un moyen plus simple de sauvegarder / restaurer shopt option / valeur.
Isaac a posté ici , comment enregistrer + restaurer Env / Shopt variable / option état / valeur.

Enregistrement de létat shopt de  » nullglob « :

... # your primary-function codes/commands, etc lines
Restauration de létat précédent du shopt de  » nullglob « , avant de quitter le script:
eval "$p_nullglob" ;

Plusieurs états shopt peuvent être enregistrés de cette façon:
p_multipleShopt="`shopt -p nullglob dotglob`";
et le processus de restauration est le même que précédemment:
eval "$p_multipleShopt" ;

Enregistrez TOUS les états shopt de cette façon:
p_allShopt="`shopt -p`";
et le processus de restauration est le même que précédemment:
eval "$p_allShopt" ;

Voici donc une autre solution basée sur bash:

 #!/bin/bash DIR="/home/john/my directory/"; p_allShopt="`shopt -p`"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; eval "$p_allShopt" ; unset DIR p_allShopt;  

Utiliser eval est sûr dans ci-dessus, car la variable "$p_allShopt" ne contient pas de données fournies par un utilisateur ou des données qui ne sont pas – nettoyé, ce var contient la sortie de la commande interne bash shopt.
Si vous voulez toujours éviter eval, utilisez ci-dessous pour lution:

 #!/bin/bash DIR="/home/john/my directory/"; p_allShopt="`shopt -p`"; shopt -s nullglob dotglob; for f in "$DIR"*.txt ; do { echo "${f}"; # your codes/commands, etc }; done; while IFS= read -a oneLine ; do { ${oneLine} ; }; done < <(echo "$p_allShopt") ; unset DIR p_allShopt oneLine;  

Quelques (autres) notables & associés SHOPT qui peuvent être utiles, sont:

  • nocaseglob: si défini, Bash fait correspondre les noms de fichiers dans un mode insensible à la casse lors de lexpansion du nom de fichier.
  • nocasematch: si défini, Bash fait correspondre les modèles de manière insensible à la casse lors de lexécution de la correspondance lors de lexécution de case ou [[ commandes conditionnelles, lors de lexécution dexpansions de mots de substitution de modèle, ou lors du filtrage de complétions possibles dans le cadre de la complétion programmable.
  • dotglob: si défini, Bash inclut les noms de fichiers commençant par ‘.’ dans les résultats de lexpansion du nom de fichier. Les noms de fichiers ‘.’ et ‘..’ doivent toujours être mis en correspondance de manière explicite, même si dotglob est défini.
  • nullglob: si défini , Bash permet aux modèles de noms de fichiers qui ne correspondent à aucun fichier de se développer en une chaîne nulle, plutôt queux-mêmes.
  • extglob: Si elle est définie, les fonctionnalités étendues de correspondance de modèle décrites ci-dessus (voir Pattern Matching ) sont activés.
  • globstar: Sil est défini, le modèle ‘**’ utilisé dans un contexte dexpansion de nom de fichier correspondra à tous les fichiers et zéro ou plusieurs répertoires et sous-répertoires. Si le modèle est suivi dun ‘/’, seuls les répertoires et sous-répertoires correspondent.

Réponse

Lorsque vous souhaitez traiter un ensemble de fichiers, vous considérez que leur nom peut être un espace ou un autre code scape sera là, donc avant de démarrer votre processus tel que for loop ou find command définissez IFS bash env variable sur:

IFS=$(echo -en "\n\b") 

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *