web-dev-qa-db-fra.com

Comment passer une chaîne à une commande qui attend un fichier?

Supposons qu'un programme cook prenne un argument: le chemin d'accès d'un fichier texte contenant la recette de l'aliment à cuisiner. Supposons que je souhaite appeler ce programme à partir d'un script bash, supposons également que j'ai déjà la recette dans une variable de chaîne:

#!/bin/bash

the_recipe="$(cat << EOF
wash cucumbers
wash knife
slice cucumbers
EOF
)"

cook ...  # What should I do here? It expects a file, but I only have a string.

Comment puis-je passer la recette à la commande lorsqu'elle attend un argument de nom de fichier?

J'ai pensé à créer un fichier temporaire juste dans le but de passer un fichier, mais je souhaite savoir s'il existe d'autres moyens de résoudre ce problème.

35
Flux

Vous pouvez utiliser le "faux" nom de fichier /dev/stdin qui représente l'entrée standard.

Alors exécutez ceci:

echo "$the_recipe" | cook /dev/stdin

La commande echo et le tube envoient le contenu de la variable spécifiée à l'entrée standard de la commande suivante cook, et cela ouvre l'entrée standard (en tant que descripteur de fichier séparé) et lit cela.

45
wurtel

Une autre façon d'utiliser la fonction de substitution de processus du bash Shell provoque la création d'un FIFO sous /tmp Ou /var/tmp, Ou utilise un descripteur de fichier nommé (/dev/fd/*), selon le système d'exploitation. La syntaxe de substitution (<(cmd)) est remplacée par le nom de FIFO ou FD, et la commande à l'intérieur est exécutée en arrière-plan.

cook <(printf '%s\n' "${the_recipe}")

La commande cook lit maintenant le contenu de la variable comme s'il était disponible dans un fichier.

Cela peut également être utilisé pour plusieurs fichiers d'entrée:

cook <(printf '%s\n' "${the_1st_recipe}") <(printf '%s\n' "${the_2nd_recipe}")
41
Inian

Cela dépend des capacités de l'application. Il peut ou non insister sur un fichier nommé, et il peut ou non insister sur un fichier recherché.

Pipe anonyme

Certaines applications lisent simplement les entrées de n'importe où. En règle générale, ils par défaut à la lecture de l'entrée standard. Si vous avez les données dans une chaîne et que vous souhaitez les transmettre à l'entrée standard de l'application, il existe plusieurs façons. Vous pouvez transmettre les informations via un tube:

printf '%s' "$the_recipe" | cook     # passes $the_recipe exactly
printf '%s\n' "$the_recipe" | cook   # passes $the_recipe plus a final newline

N'utilisez pas echo ici car, selon le shell, il pourrait modifier les antislashs.

Si l'application insiste sur un argument de fichier, elle peut accepter - pour signifier l'entrée standard. Il s'agit d'une convention commune, mais pas systématique.

printf '%s\n' "$the_recipe" | cook -

Si l'application a besoin d'un nom de fichier réel, passez /dev/stdin pour signifier l'entrée standard.

printf '%s\n' "$the_recipe" | cook /dev/stdin

Pour certaines applications, même cela ne suffit pas: ils ont besoin d'un nom de fichier avec certaines propriétés, par exemple avec une extension donnée, ou ils ont besoin d'un nom de fichier dans un répertoire accessible en écriture où ils créent des fichiers temporaires. Dans de tels cas, vous avez généralement besoin d'un fichier temporaire, bien que parfois un canal nommé soit suffisant.

Pipe nommée

Il est possible de donner un nom à une pipe. Je mentionne cela pour être complet, mais c'est rarement le moyen le plus pratique. Cela évite d'obtenir les données sur le disque. Il convient si l'application a besoin d'un fichier avec des contraintes sur le nom, mais n'a pas besoin que le fichier soit recherché.

mkfifo named.pipe
printf '%s\n' "$the_recipe" >named.pipe & writer=$!
cook named.pipe
rm named.pipe
wait "$writer"

(Vérification d'erreur omise.)

Fichier temporaire

Si l'application a besoin d'un fichier recherché , vous devez créer un fichier temporaire. Un fichier recherché est celui où l'application peut aller et venir, en lisant des portions arbitraires à la fois. Une pipe ne le permet pas: elle doit être lue du début à la fin, dans l'ordre, sans jamais reculer.

Bash, ksh et zsh ont une syntaxe pratique pour passer une chaîne en entrée via un fichier temporaire. Notez qu'ils ajoutent toujours une nouvelle ligne à la chaîne (même s'il y en a déjà une).

cook <<<"$the_recipe"

Avec d'autres shells, ou si vous souhaitez contrôler le répertoire contenant le fichier temporaire, vous devez créer le fichier temporaire manuellement. La plupart des unités fournissent un mktemp Utility pour créer le fichier temporaire en toute sécurité. (N'utilisez jamais quelque chose comme … >/tmp/temp.$$! Il permet à d'autres programmes, même exécutés en tant qu'autres utilisateurs, de détourner le fichier.) Voici un script qui crée un fichier temporaire et prend soin de le supprimer s'il est interrompu.

tmp=
trap 'rm -f "$tmp"' EXIT
trap 'rm -f "$tmp"; trap "" HUP; kill -INT $$' HUP
trap 'rm -f "$tmp"; trap "" INT; kill -INT $$' INT
trap 'rm -f "$tmp"; trap "" TERM; kill -INT $$' TERM
tmp=$(mktemp)
printf '%s\n' "$the_recipe" >"$tmp"
cook "$tmp"

Pour choisir où créer le fichier temporaire, définissez la variable d'environnement TMPDIR pour l'appel mktemp. Par exemple, pour créer le fichier temporaire dans le répertoire en cours plutôt que l'emplacement par défaut des fichiers temporaires:

tmp=$(TMPDIR="$PWD" mktemp)

(En utilisant $PWD plutôt que . vous donne un nom de fichier absolu, ce qui signifie que vous pouvez appeler en toute sécurité cd dans votre script sans craindre que $tmp ne désignera plus le même fichier.)

Si l'application a besoin d'un nom de fichier avec une certaine extension, vous devez créer un répertoire temporaire et y créer un fichier. Pour créer le répertoire temporaire, utilisez mktemp -d. Encore une fois, définissez la variable d'environnement TMPDIR si vous souhaitez contrôler où le répertoire temporaire est créé.

tmp=
trap 'rm -rf "$tmp"' EXIT
trap 'rm -rf "$tmp"; trap "" HUP; kill -INT $$' HUP
trap 'rm -rf "$tmp"; trap "" INT; kill -INT $$' INT
trap 'rm -rf "$tmp"; trap "" TERM; kill -INT $$' TERM
tmp=$(mktemp -d)
printf '%s\n' "$the_recipe" >"$tmp/myfile.ext"
cook "$tmp/myfile.ext"

Vérifiez toujours si votre version de cook prend en charge l'argument - (tiret) comme nom de fichier avant d'essayer des solutions plus compliquées. Si cette convention est prise en charge, elle indique à cook de lire à partir de l'entrée standard, comme ceci:

echo "$the_recipe" | cook -

ou

cook - <<<"$the_recipe"
2
rackandboneman