web-dev-qa-db-fra.com

Pourquoi je ne peux pas échapper aux espaces sur un script bash?

J'essaie d'échapper au caractère d'espace pour un chemin dans Bash, mais ni en utilisant une barre oblique inverse ou guillemets fonctionne.

Script .sh:

ROOT="/home/hogar/Documents/files/"
FILE=${ROOT}"bdd.encrypted"
DESTINATION="/home/hogar/Ubuntu\ One/folder"

mv ${FILE} ${DESTINATION}

Après avoir exécuté le script (./file) voici le résultat:

mv: target 'One/folder' is not a directory

Pourquoi la commande mv divise-t-elle la chaîne et comment puis-je empêcher que cela se produise?

72
Lucio

Vous développez la variable DESTINATION, si vous aviez echo voici ce que vous obtiendriez:

echo ${DESTINATION}
/home/hogar/Ubuntu\ One/folder

Mais mv ne comprend pas ceci:

mv ${FILE} ${DESTINATION}                                                
mv: cannot move '/home/hogar/Documents/files/bdd.encrypted' to '/home/hogar/Ubuntu\\ One/folder': No such file or directory

(pour une raison quelconque, mon mv est plus verbeux)

Pour éviter cela, vous devez utiliser des guillemets à la place:

mv "${FILE}" "${DESTINATION}"

Si vous n'avez pas besoin d'extension (puisque vous étendez déjà avant), utilisez simplement "$..." devrait suffire:

mv "$FILE" "$DESTINATION"
84
Braiam

Votre étape pour préparer les variables est "assez proche" et fonctionnera, mais elle montre une faiblesse dans la compréhension (c'est pourquoi vous avez posté!)

L'affectation à DESTINATION ne doit être que l'une des deux suivantes:

DESTINATION="/home/hogar/Ubuntu One/folder"

ou

DESTINATION=/home/hogar/Ubuntu\ One/folder

Les guillemets doubles activent les guillemets (un formulaire qui a de la magie) et la barre oblique inverse est capable de citer un seul caractère qui le suit. Personnellement, je pense que le formulaire utilisant des guillemets doubles est préférable car visuellement c'est plus agréable.

Soit dit en passant, pour des raisons similaires, je ferais la tâche FILE comme:

FILE="${ROOT}bdd.encrypted"

ce qui montre l'occasion assez rare d'utiliser ${NAME} au lieu de simplement $NAME - pour séparer le nom de la variable de toute lettre suivante. En fait, si vous n'aviez pas de fin/sur la définition de ROOT, j'écrirais

FILE="$ROOT/bdd.encrypted"

qui a l'air encore mieux. Cela vous donnera également des avantages occasionnels dans lesquels je n'entrerai pas; disons simplement d'après mon expérience, les barres obliques de fin ont tendance à être plus indésirables que les bienvenues, et elles ne sont certainement pas nécessaires. (Cela a des conséquences en ce qui concerne les liens symboliques, mais c'est certainement trop de détails sur cette réponse déjà volumineuse).

Le nœud de votre problème se produit en fait à la commande mv, qui devrait être écrite comme

mv "$FILE" "$DESTINATION"

car de nos jours, on ne sait jamais quand une variable représentant un chemin aura des espaces dedans. Encore une fois, notez l'évitement des accolades pour une simple expansion variable.

La raison pour laquelle vous avez rencontré un problème est due au processus utilisé par le shell pour créer des lignes de commande. Si vous lisez attentivement le manuel de Shell, il dit essentiellement que les variables (et quelques autres choses) sont développées PUIS il va chercher des espaces. Bien sûr, il y a un peu plus de complexité dans le processus, mais c'est l'essentiel du problème pour vous.

Un autre point qui est lié et mérite d'être connu: la distinction entre $*, $@, "$*" et "$@". Je vais donc en parler!

Si vous avez un script Shell qui peut être appelé comme:

foo -x "attached is the software" "please read the accompanying manual"

alors foo verra -x comme $1, et les deux chaînes comme $2 et $3 (auquel vous devez accéder en tant que "$2" et "$3" pour des raisons évidentes). Si, cependant, vous souhaitez transmettre cet ensemble de paramètres à un autre script, les éléments suivants subiront un fractionnement horrible sur tous les espaces:

bar $*

et ce qui suit (qui était le seul moyen dans la version 0.X du Bourn Shell très original) fera passer tous les arguments en une seule chaîne (également FAUX)

bar "$*"

donc la syntaxe suivante a été ajoutée comme cas magique très spécial:

bar "$@"

qui cite délibérément les membres individuels de $* en tant que chaînes complètes entre guillemets mais les garde séparées. Tout le monde est gagnant maintenant.

C'est un peu amusant de tester d'autres effets de $@ lorsqu'il n'est pas utilisé comme "$@" mais vous découvrirez rapidement que ce n'est pas vraiment divertissant ... c'est simplement un cas spécial qui résout un problème majeur.

Tous les shells modernes décents qui prennent en charge les tableaux utilisent également @ dans un cas spécial:

${arr[*]}
"${arr[*]}"
"${arr[@]}"

où le premier souffrira de la division sur tous les espaces, conduisant à un nombre imprévisible de mots séparés, le second produira tous les membres du tableau dans une chaîne, et le troisième offrira très bien chaque membre du tableau en tant que chaîne individuelle non interrompue entre guillemets.

Prendre plaisir!

27
user56373

Oui, vous avez échappé à l'espace lorsque vous attribuez la valeur à $DESTINATION,

Mais lorsque vous l'utilisez avec la commande mv, vous ne l'avez pas

mv ${FILE} ${DESTINATION}

Utilisez-le à la place:

mv "${FILE}" "${DESTINATION}"
13
daisy