Coquille une doublure à ajouter à un fichier


131

C'est probablement une solution complexe .

Je recherche un opérateur simple comme ">>", mais en préfixe.

J'ai peur que cela n'existe pas. Je vais devoir faire quelque chose comme

 mv myfile tmp
 cat myheader tmp> myfile

Quelque chose de plus intelligent?


Quel est le problème avec mktemp? Vous pouvez toujours nettoyer le fichier temporaire par la suite ...
candu

Réponses:


29

Le hack ci-dessous était une réponse rapide et spontanée qui a fonctionné et a reçu beaucoup de votes positifs. Puis, au fur et à mesure que la question devenait plus populaire et que le temps passait, des gens indignés ont commencé à signaler que cela fonctionnait en quelque sorte mais que des choses étranges pouvaient arriver, ou que cela ne fonctionnait tout simplement pas, alors il a été furieusement déclassé pendant un certain temps. Si amusant.

La solution exploite l'implémentation exacte des descripteurs de fichiers sur votre système et, comme l'implémentation varie considérablement entre les nœuds, son succès dépend entièrement du système, définitivement non portable et ne doit pas être invoqué pour quelque chose, même vaguement important.

Maintenant, avec tout cela à l'écart, la réponse était:


Créer un autre descripteur de fichier pour le fichier ( exec 3<> yourfile) puis écrire dans ce ( >&3) semble surmonter le dilemme de lecture / écriture sur le même fichier. Fonctionne pour moi sur des fichiers 600K avec awk. Cependant, essayer la même astuce en utilisant «chat» échoue.

Passer le préfixe comme variable à awk ( -v TEXT="$text") résout le problème des guillemets littéraux qui empêche de faire cette astuce avec 'sed'.

#!/bin/bash
text="Hello world
What's up?"

exec 3<> yourfile && awk -v TEXT="$text" 'BEGIN {print TEXT}{print}' yourfile >&3

3
AVERTISSEMENT: vérifiez la sortie pour vous assurer que rien n'a changé sauf la première ligne. J'ai utilisé cette solution, et même si cela semblait fonctionner, j'ai remarqué que de nouvelles lignes semblaient être ajoutées. Je ne comprends pas d'où ils viennent, donc je ne peux pas être sûr que cette solution pose vraiment un problème, mais méfiez-vous.
conradlee

1
Notez dans l'exemple bash ci-dessus que les guillemets doubles s'étendent sur le retour chariot afin de montrer le préfixe de plusieurs lignes. Vérifiez que votre shell et votre éditeur de texte sont coopératifs. \ r \ n vient à l'esprit.
John Mee

4
Utilisez ceci avec prudence! Voir le commentaire suivant pour savoir pourquoi: stackoverflow.com/questions/54365/…
Alex

3
Si elle n'est pas fiable, que diriez-vous de mettre à jour votre réponse avec une meilleure solution?
zakdances

Un hack est utile si et seulement si on sait précisément pourquoi il fonctionne et, par conséquent, sous quelles contraintes soigneusement observées . Nonobstant votre clause de non-responsabilité, votre hack ne répond pas à ces critères («entièrement dépendant du système», «semble surmonter», «fonctionne pour moi»). Si nous prenons votre clause de non-responsabilité au sérieux, personne ne devrait utiliser votre hack - et je suis d'accord. Alors, que nous reste-t-il? Une distraction bruyante. Je vois deux options: (a) supprimer votre réponse, ou (b) en faire un récit édifiant qui explique pourquoi - aussi tentante que cela puisse être - cette approche ne peut pas fonctionner en général .
mklement0

103

Cela utilise toujours un fichier temporaire, mais au moins il est sur une ligne:

echo "text" | cat - yourfile > /tmp/out && mv /tmp/out yourfile

Crédit: BASH: ajouter un texte / des lignes à un fichier


4
Que fais- tu -après cat?
makbol

Existe-t-il un moyen d'ajouter du «texte» sans nouvelle ligne après?
chishaku

@chishakuecho -n "text"
Sparhawk

3
Un avertissement: s'il yourfiles'agit d'un lien symbolique, cela ne fera pas ce que vous voulez.
dshepherd

La réponse est-elle différente de celle-ci: echo "text"> / tmp / out; cat votre fichier >> / tmp / out; mv / tmp / out yourfile ??
Richard


20

John Mee: votre méthode n'est pas garantie de fonctionner, et échouera probablement si vous ajoutez plus de 4096 octets de trucs (du moins c'est ce qui se passe avec gnu awk, mais je suppose que d'autres implémentations auront des contraintes similaires). Non seulement il échouera dans ce cas, mais il entrera dans une boucle sans fin où il lira sa propre sortie, faisant ainsi grossir le fichier jusqu'à ce que tout l'espace disponible soit rempli.

Essayez-le par vous-même:

exec 3<>myfile && awk 'BEGIN{for(i=1;i<=1100;i++)print i}{print}' myfile >&3

(avertissement: tuez-le après un certain temps ou il remplira le système de fichiers)

De plus, il est très dangereux d'éditer des fichiers de cette façon, et c'est un très mauvais conseil, comme si quelque chose se passait pendant que le fichier était en cours d'édition (plantage, disque plein), vous êtes presque assuré de rester avec le fichier dans un état incohérent.


6
Cela devrait probablement être un commentaire, pas une réponse distincte.
lkraav

10
@Ikraav: peut-être, mais le "commentaire" est très long et élaboré (et utile), il n'aura pas l'air bien et ne correspond peut-être pas à la capacité de commentaires limitée de StackOverflow de toute façon.
Hendy Irawan

18

Pas possible sans fichier temporaire, mais voici un oneliner

{ echo foo; cat oldfile; } > newfile && mv newfile oldfile

Vous pouvez utiliser d'autres outils tels que ed ou perl pour le faire sans fichiers temporaires.


16

Il peut être intéressant de noter que c'est souvent une bonne idée de générer en toute sécurité le fichier temporaire à l'aide d'un utilitaire comme mktemp , du moins si le script sera un jour exécuté avec les privilèges root. Vous pouvez par exemple faire ce qui suit (encore une fois dans bash):

(tmpfile=`mktemp` && { echo "prepended text" | cat - yourfile > $tmpfile && mv $tmpfile yourfile; } )

15

Si vous en avez besoin sur les ordinateurs que vous contrôlez, installez le paquet "moreutils" et utilisez "sponge". Ensuite, vous pouvez faire:

cat header myfile | sponge myfile

Cela a bien fonctionné pour moi lorsqu'il est combiné avec la réponse de @Vinko Vrsalovic:{ echo "prepended text"; cat myfile } | sponge myfile
Jesse Hallett

13

En utilisant un heredoc bash, vous pouvez éviter le besoin d'un fichier tmp:

cat <<-EOF > myfile
  $(echo this is prepended)
  $(cat myfile)
EOF

Cela fonctionne car $ (cat myfile) est évalué lorsque le script bash est évalué, avant que le chat avec redirection ne soit exécuté.


9

en supposant que le fichier que vous souhaitez modifier est my.txt

$cat my.txt    
this is the regular file

Et le fichier que vous souhaitez ajouter est en-tête

$ cat header
this is the header

Assurez-vous d'avoir une dernière ligne vierge dans le fichier d'en-tête.
Maintenant, vous pouvez l'ajouter avec

$cat header <(cat my.txt) > my.txt

Vous vous retrouvez avec

$ cat my.txt
this is the header
this is the regular file

Autant que je sache, cela ne fonctionne que dans «bash».


pourquoi ça marche? Les parenthèses obligent-elles le chat du milieu à s'exécuter dans un shell séparé et à vider le fichier entier en même temps?
Catskul

Je pense que le <(cat my.txt) crée un fichier temporaire quelque part dans le système de fichiers. Ce fichier est ensuite lu avant que le fichier d'origine ne soit modifié.
cb0

Quelqu'un m'a montré une fois ce que vous pouvez faire avec la syntaxe "<()". Cependant, je n'ai jamais appris comment cette méthode est appelée et je n'ai pas pu trouver quelque chose dans la page de manuel bash. Si quelqu'un en sait plus, faites-le moi savoir.
cb0

1
Cela n'a pas fonctionné pour moi. Je ne sais pas pourquoi. J'utilise GNU bash, version 3.2.57 (1) -release (x86_64-apple-darwin14), je suis sur OS X Yosemite. Je me suis retrouvé avec deux lignes de this is the headerdans my.txt. Même après avoir mis à jour Bash pour 4.3.42(1)-releaseobtenir le même résultat.
Kohányi Róbert

1
Bash ne crée pas de fichier temporaire, il crée un FIFO (pipe) auquel la commande dans le processus substitution ( <(...)) écrit, il n'y a donc aucune garantie qu'il my.txtsoit lu en entier à l'avance , sans lequel cette technique ne fonctionnera pas.
mklement0

9

Lorsque vous commencez à essayer de faire des choses qui deviennent difficiles dans un script shell, je suggère fortement de chercher à réécrire le script dans un langage de script "approprié" (Python / Perl / Ruby / etc)

En ce qui concerne l'ajout d'une ligne à un fichier, il n'est pas possible de le faire via un tuyau, car lorsque vous faites quelque chose de similaire cat blah.txt | grep something > blah.txt, cela vide le fichier par inadvertance. Il existe une petite commande utilitaire appelée que spongevous pouvez installer (vous le faites cat blah.txt | grep something | sponge blah.txtet elle met en mémoire tampon le contenu du fichier, puis l'écrit dans le fichier). Il est similaire à un fichier temporaire mais vous n'avez pas à le faire explicitement. mais je dirais que c'est une condition "pire" que, disons, Perl.

Il peut y avoir un moyen de le faire via awk, ou similaire, mais si vous devez utiliser un script shell, je pense qu'un fichier temporaire est de loin le moyen le plus simple (/ seulement?) ..


7

Comme le suggère Daniel Velkov, utilisez un tee.
Pour moi, c'est une solution simple et intelligente:

{ echo foo; cat bar; } | tee bar > /dev/null

7

EDIT: C'est cassé. Voir un comportement étrange lors de l'ajout d'un fichier avec chat et tee

La solution de contournement au problème d'écrasement consiste à utiliser tee:

cat header main | tee main > /dev/null

Accroché pendant un moment. Terminé par "tee: main: aucun espace laissé sur l'appareil". main était de plusieurs Go, bien que les deux fichiers texte d'origine ne contenaient que quelques Ko.
Marcos

3
Si la solution que vous avez publiée est défectueuse (et remplit en fait les disques durs des utilisateurs), rendez service à la communauté et supprimez votre réponse.
aioobe le

1
Pouvez-vous m'indiquer une ligne directrice qui dit que les mauvaises réponses devraient être supprimées?
Daniel Velkov du

3

Celui que j'utilise. Celui-ci vous permet de spécifier l'ordre, les caractères supplémentaires, etc. comme vous le souhaitez:

echo -e "TEXTFIRSt\n$(< header)\n$(< my.txt)" > my.txt

PS: seulement cela ne fonctionne pas si les fichiers contiennent du texte avec une barre oblique inverse, car il est interprété comme des caractères d'échappement


3

Surtout pour le plaisir / golf, mais

ex -c '0r myheader|x' myfile

fera l'affaire, et il n'y a pas de pipelines ou de redirections. Bien sûr, vi / ex n'est pas vraiment destiné à une utilisation non interactive, donc vi clignotera brièvement.


Meilleure recette de mon point de vue car elle utilise une commande en place et le fait de manière simple.
Diego

2

Pourquoi ne pas simplement utiliser la commande ed (comme déjà suggéré par fluffle ici)?

ed lit le fichier entier en mémoire et effectue automatiquement une édition de fichier sur place!

Donc, si votre fichier n'est pas si énorme ...

# cf. "Editing files with the ed text editor from scripts.",
# http://wiki.bash-hackers.org/doku.php?id=howto:edit-ed

prepend() {
   printf '%s\n' H 1i "${1}" . wq | ed -s "${2}"
}

echo 'Hello, world!' > myfile
prepend 'line to prepend' myfile

Une autre solution de contournement consisterait à utiliser des descripteurs de fichiers ouverts comme suggéré par Jürgen Hötzel dans Rediriger la sortie de sed 's / c / d /' myFile vers myFile

echo cat > manipulate.txt
exec 3<manipulate.txt
# Prevent open file from being truncated:
rm manipulate.txt
sed 's/cat/dog/' <&3 > manipulate.txt

Tout cela pourrait être mis sur une seule ligne, bien sûr.


2

Une variante de la solution de cb0 pour "pas de fichier temporaire" pour ajouter du texte fixe:

echo "text to prepend" | cat - file_to_be_modified | ( cat > file_to_be_modified ) 

Encore une fois, cela repose sur l'exécution du sous-shell - le (..) - pour éviter que le chat refuse d'avoir le même fichier pour l'entrée et la sortie.

Remarque: j'ai aimé cette solution. Cependant, sur mon Mac, le fichier d'origine est perdu (je pense qu'il ne devrait pas, mais c'est le cas). Cela pourrait être résolu en écrivant votre solution sous la forme: echo "text to prepend" | cat - file_to_be_modified | cat> tmp_file; mv tmp_file file_to_be_modified


1
Moi aussi, je trouve le fichier original perdu. Ubuntu Lucid.
Hedgehog

2

Voici ce que j'ai découvert:

echo -e "header \n$(cat file)" >file

1
C'est la même chose que la suggestion précédente de @nemisj Non?
Hedgehog

2
sed -i -e '1rmyheader' -e '1{h;d}' -e '2{x;G}' myfile

2

ATTENTION: cela nécessite un peu plus de travail pour répondre aux besoins du PO.

Il devrait y avoir un moyen de faire fonctionner l'approche sed de @shixilun malgré ses réticences. Il doit y avoir une commande bash pour échapper les espaces lors de la lecture d' un fichier dans une chaîne de substitution sed (par exemple remplacer des caractères de nouvelle ligne avec « \ n ». Les commandes shell viset catpeut traiter avec des caractères non imprimables, mais pas blancs, donc cela ne résoudra pas les années OP problème:

sed -i -e "1s/^/$(cat file_with_header.txt)/" file_to_be_prepended.txt

échoue en raison des nouvelles lignes brutes dans le script de remplacement, qui doivent être précédées d'un caractère de continuation de ligne () et peut-être suivi d'un &, pour que le shell et sed soient heureux, comme cette réponse SO

sed a une taille limite de 40K pour les commandes de recherche-remplacement non globales (pas de / g de fin après le modèle), ce qui éviterait probablement les problèmes effrayants de dépassement de tampon de awk dont anonyme a averti.


2
sed -i -e "1s/^/new first line\n/" old_file.txt

exactement ce que je cherchais, mais en effet pas la bonne réponse à la question
masterxilo

2

Avec $ (command), vous pouvez écrire la sortie d'une commande dans une variable. Je l'ai donc fait en trois commandes sur une seule ligne et sans fichier temporaire.

originalContent=$(cat targetfile) && echo "text to prepend" > targetfile && echo "$originalContent" >> targetfile

1

Si vous avez un gros fichier (quelques centaines de kilo-octets dans mon cas) et un accès à python, c'est beaucoup plus rapide que les catsolutions de pipe:

python -c 'f = "filename"; t = open(f).read(); open(f, "w").write("text to prepend " + t)'


1

Une solution avec printf:

new_line='the line you want to add'
target_file='/file you/want to/write to'

printf "%s\n$(cat ${target_file})" "${new_line}" > "${target_file}"

Vous pouvez également faire:

printf "${new_line}\n$(cat ${target_file})" > "${target_file}"

Mais dans ce cas, vous devez vous assurer qu'il n'y en a pas n'importe %où, y compris le contenu du fichier cible, car cela peut être interprété et bousiller vos résultats.


2
Cela semble exploser (et tronquer votre fichier!) Si vous avez quelque chose dans votre fichier cible que printf interprète comme une option de formatage.
Alan H.

echosemble être une option plus sûre. echo "my new line\n$(cat my/file.txt)" > my/file.txt
Alan H.

@AlanH. Je préviens des dangers dans la réponse.
user137369

En fait, vous n'avez averti que des pourcentages ${new_line}, pas du fichier cible
Alan H.

Pourriez-vous être plus explicite? C'est une très grosse mise en garde de l'OMI. «N'importe où» ne le rend pas très clair.
Alan H.

1

Vous pouvez utiliser la ligne de commande perl:

perl -i -0777 -pe 's/^/my_header/' tmp

Où -i créera un remplacement en ligne du fichier et -0777 slurp le fichier entier et fera correspondre ^ uniquement le début. -pe imprimera toutes les lignes

Ou si my_header est un fichier:

perl -i -0777 -pe 's/^/`cat my_header`/e' tmp

Où le / e permettra une évaluation du code dans la substitution.


0
current=`cat my_file` && echo 'my_string' > my_file && echo $current >> my_file

où "mon_fichier" est le fichier auquel ajouter "ma_ chaîne".


0

J'aime l' approche ed de @ fluffle le mieux l' . Après tout, les commutateurs de ligne de commande de n'importe quel outil par rapport aux commandes de l'éditeur scripté sont essentiellement la même chose ici; ne pas voir une solution d'éditeur scripté "propreté" étant moindre ou quoi que ce soit.

Voici mon one-liner ajouté pour .git/hooks/prepare-commit-msgajouter un .gitmessagefichier dans le dépôt pour valider les messages:

echo -e "1r $PWD/.gitmessage\n.\nw" | ed -s "$1"

Exemple .gitmessage:

# Commit message formatting samples:
#       runlevels: boot +consolekit -zfs-fuse
#

Je fais à la 1rplace de 0r, car cela laissera la ligne vide prête à écrire au-dessus du fichier du modèle d'origine. Ne mettez pas une ligne vide au-dessus de votre .gitmessagealors, vous vous retrouverez avec deux lignes vides.-ssupprime la sortie des informations de diagnostic de ed.

En relation avec cela, j'ai découvert que pour les amateurs de vim, il est également bon d'avoir:

[core]
        editor = vim -c ':normal gg'

0

variables, ftw?

NEWFILE=$(echo deb http://mirror.csesoc.unsw.edu.au/ubuntu/ $(lsb_release -cs) main universe restricted multiverse && cat /etc/apt/sources.list)
echo "$NEWFILE" | sudo tee /etc/apt/sources.list

0

Je pense que c'est la variante la plus propre d'ed:

cat myheader | { echo '0a'; cat ; echo -e ".\nw";} | ed myfile

en tant que fonction:

function prepend() { { echo '0a'; cat ; echo -e ".\nw";} | ed $1; }

cat myheader | prepend myfile

0

Si vous créez un script dans BASH, en fait, vous pouvez simplement émettre:

cat - votre fichier / tmp / sortie && mv / tmp / sortie votre fichier

C'est en fait dans l'exemple complexe que vous avez vous-même publié dans votre propre question.


Un éditeur a suggéré de changer cela en cat - yourfile <<<"text" > /tmp/out && mv /tmp/out yourfile, mais c'est suffisamment différent de ma réponse pour que ce soit sa propre réponse.
Tim Kennedy

0

À mon humble avis, il n'y a pas de solution shell (et n'en sera jamais une) qui fonctionnerait de manière cohérente et fiable quelle que soit la taille des deux fichiers myheaderet myfile. La raison en est que si vous voulez faire cela sans revenir à un fichier temporaire (et sans laisser le shell se répéter silencieusement dans un fichier temporaire, par exemple via des constructions comme exec 3<>myfile, piping totee , etc.

La "vraie" solution que vous recherchez doit manipuler le système de fichiers, elle n'est donc pas disponible dans l'espace utilisateur et dépendra de la plate-forme: vous demandez de modifier le pointeur du système de fichiers utilisé par myfilela valeur actuelle du pointeur du système de fichiers pour myheaderet remplacer dans le système de fichier EOFde myheaderavec un lien enchaînée à l'adresse du système de fichiers en cours pointé par myfile. Ce n'est pas anodin et ne peut évidemment pas être fait par un non-super-utilisateur, et probablement pas par le super-utilisateur non plus ... Jouez avec les inodes, etc.

Cependant, vous pouvez plus ou moins simuler cela en utilisant des périphériques en boucle. Voir par exemple ce fil SO .

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.