Je voudrais mettre à jour un grand nombre de fichiers source C ++ avec une directive include supplémentaire avant tout #includes existant. Pour ce genre de tâche, j'utilise normalement un petit script bash avec sed pour réécrire le fichier.
Comment puis-je sed
remplacer uniquement la première occurrence d'une chaîne dans un fichier plutôt que de remplacer chaque occurrence?
Si j'utilise
sed s/#include/#include "newfile.h"\n#include/
il remplace tous les #includes.
Des suggestions alternatives pour réaliser la même chose sont également les bienvenues.
command-line
sed
text-processing
David Dibben
la source
la source
0,
ne fonctionne qu'avecgnu sed
s//
- c'est-à-dire une expression régulière vide - signifie que l' expression régulière la plus récemment appliquée est implicitement réutilisée; dans ce casRE
,. Ce raccourci pratique signifie que vous n'avez pas à dupliquer l'expression régulière de fin de plage dans votres
appel.Un
sed
script qui ne remplacera que la première occurrence de "Apple" par "Banana"Exemple
Voici le script simple: Note de l'éditeur: fonctionne uniquement avec GNU
sed
.Les deux premiers paramètres
0
et/Apple/
sont le spécificateur de plage. C'ests/Apple/Banana/
ce qui est exécuté dans cette plage. Donc dans ce cas "dans la plage du début (0
) jusqu'à la première instance deApple
, remplacezApple
parBanana
. Seul le premierApple
sera remplacé.Contexte: En traditionnel,
sed
le spécificateur de plage est également "commencer ici" et "terminer ici" (inclus). Cependant, le "début" le plus bas est la première ligne (ligne 1), et si la "fin ici" est une expression régulière, alors il est uniquement tenté de faire correspondre la ligne suivante après le "début", donc la fin la plus précoce possible est la ligne 2. Donc, comme la plage est inclusive, la plus petite plage possible est "2 lignes" et la plus petite plage de départ est à la fois les lignes 1 et 2 (c'est-à-dire s'il y a une occurrence sur la ligne 1, les occurrences sur la ligne 2 seront également modifiées, ce qui n'est pas souhaité dans ce cas). ).GNU
sed ajoute sa propre extension permettant de spécifier start comme "pseudo"line 0
afin que la fin de la plage puisse êtreline 1
, lui permettant une plage de "seulement la première ligne"Ou une version simplifiée (un RE comme un
//
moyen vide pour réutiliser celui spécifié avant, donc c'est équivalent):Et les accolades sont facultatives pour la
s
commande, c'est donc également équivalent:Tous ces éléments fonctionnent
sed
uniquement sur GNU .Vous pouvez également installer GNU sed sur OS X en utilisant homebrew
brew install gnu-sed
.la source
sed: 1: "…": bad flag in substitute command: '}'
sed -e '1s/Apple/Banana/;t' -e '1,/Apple/s//Banana/'
. De la réponse de @ MikhailVS (actuellement) en bas ci-dessous.sed '0,/foo/s/foo/bar/'
sed: -e expression #1, char 3: unexpected
'', aveccela a fonctionné pour moi.
exemple
Note de l'éditeur: les deux fonctionnent uniquement avec GNU
sed
.la source
sed '1,/pattern/s/pattern/replacement/' filename
ne fonctionne que si "le motif ne se produira pas sur la première ligne" sur Mac. Je vais supprimer mon commentaire précédent car il n'est pas exact. Le détail peut être trouvé ici ( linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/… ). La réponse d'Andy ne fonctionne que pour GNU sed, mais pas celle sur Mac.Un aperçu des nombreuses réponses utiles existantes , complété par des explications :
Les exemples ici utilisent un cas d'utilisation simplifié: remplacez le mot «foo» par «bar» dans la première ligne correspondante uniquement.
En raison de l' utilisation de la norme ANSI chaînes C-cités (
$'...'
) pour fournir des lignes d'entrée échantillon,bash
,ksh
, ouzsh
est supposé que la coquille.GNU
sed
uniquement:La réponse de Ben Hoffstein nous montre que GNU fournit une extension à la spécification POSIX car elle
sed
permet la forme à 2 adresses suivante :0,/re/
(re
représente ici une expression régulière arbitraire).0,/re/
permet également à l'expression régulière de correspondre sur la toute première ligne . En d'autres termes: une telle adresse créera une plage de la 1ère ligne jusqu'à et y compris la ligne qui correspondre
- qu'ellere
se produise sur la 1ère ligne ou sur toute ligne suivante.1,/re/
, qui crée une plage qui correspond de la 1ère ligne jusqu'à et y compris la ligne qui correspondre
sur les lignes suivantes ; en d'autres termes: cela ne détectera pas la première occurrence d'unere
correspondance s'il se produit sur la 1ère ligne et empêche//
également l'utilisation de sténographie pour la réutilisation de l'expression régulière la plus récemment utilisée (voir point suivant). 1Si vous combinez une
0,/re/
adresse avec uns/.../.../
appel (substitution) qui utilise la même expression régulière, votre commande n'effectuera effectivement la substitution que sur la première ligne correspondantere
.sed
fournit un moyen pratique raccourci pour réutiliser l'expression régulière plus récemment appliquée : un vide paire delimiter,//
.Un POSIX-fonctionnalités uniquement
sed
telles que BSD (macOS)sed
(fonctionnera également avec GNUsed
):Puisqu'il
0,/re/
ne peut pas être utilisé et que le formulaire1,/re/
ne détectera pasre
s'il se produit sur la toute première ligne (voir ci-dessus), une gestion spéciale pour la 1ère ligne est requise .La réponse de MikhailVS mentionne la technique, mise dans un exemple concret ici:
Remarque:
Le
//
raccourci d' expression régulière vide est utilisé deux fois ici: une fois pour le point d'extrémité de la plage, et une fois dans l's
appel; dans les deux cas, l'expression régulièrefoo
est implicitement réutilisée, ce qui nous permet de ne pas avoir à la dupliquer, ce qui rend le code plus court et plus facile à maintenir.POSIX a
sed
besoin de nouvelles lignes après certaines fonctions, comme le nom d'une étiquette ou même son omission, comme c'est le cast
ici; Le fractionnement stratégique du script en plusieurs-e
options est une alternative à l'utilisation d'un retour à la ligne réel: terminez chaque-e
bloc de script là où un retour à la ligne devrait normalement aller.1 s/foo/bar/
remplacefoo
uniquement sur la 1ère ligne, s'il y est trouvé. Si c'est le cas, set
ramifie à la fin du script (ignore les commandes restantes sur la ligne). (Lat
fonction se branche sur une étiquette uniquement si l's
appel le plus récent a effectué une substitution réelle; en l'absence d'une étiquette, comme c'est le cas ici, la fin du script est branchée sur).Lorsque cela se produit, l'adresse de plage
1,//
, qui trouve normalement la première occurrence à partir de la ligne 2 , ne correspondra pas et la plage ne sera pas traitée, car l'adresse est évaluée lorsque la ligne actuelle est déjà2
.Inversement, s'il n'y a pas de correspondance sur la 1ère ligne,
1,//
sera entré et trouvera la vraie première correspondance.L'effet net est le même que celui avec GNU
sed
de0,/re/
: seule la première occurrence est remplacée, si elle se produit sur la 1ère ligne ou tout autre.Approches sans portée
la réponse de potong démontre des techniques de boucle qui contournent le besoin d'une gamme ; puisqu'il utilise la syntaxe GNU
sed
, voici les équivalents compatibles POSIX :Technique de boucle 1: lors de la première correspondance, effectuez la substitution, puis entrez une boucle qui imprime simplement les lignes restantes telles quelles :
Technique de boucle 2, pour les petits fichiers uniquement : lisez l'intégralité de l'entrée en mémoire, puis effectuez une seule substitution dessus .
1 1.61803 donne des exemples de ce qui se passe avec
1,/re/
, avec et sans suites//
:-
sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'
rendements$'1bar\n2bar'
; c'est-à-dire que les deux lignes ont été mises à jour, car le numéro de ligne1
correspond à la 1ère ligne, et l'expression régulière/foo/
- la fin de la plage - n'est alors recherchée qu'à partir de la ligne suivante . Par conséquent, les deux lignes sont sélectionnées dans ce cas et las/foo/bar/
substitution est effectuée sur les deux.-
sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo'
échoue : avecsed: first RE may not be empty
(BSD / macOS) etsed: -e expression #1, char 0: no previous regular expression
(GNU), car, au moment où la 1ère ligne est traitée (en raison du numéro de ligne1
commençant la plage), aucune expression régulière n'a encore été appliquée, donc//
ne fait référence à rien.À l'exception de
sed
la0,/re/
syntaxe spéciale de GNU , toute plage commençant par un numéro de ligne empêche effectivement l'utilisation de//
.la source
Vous pouvez utiliser awk pour faire quelque chose de similaire ..
Explication:
Exécute l'instruction d'action entre {} lorsque la ligne correspond à "#include" et que nous ne l'avons pas déjà traitée.
Cela affiche #include "newfile.h", nous devons échapper aux guillemets. Ensuite, nous définissons la variable done sur 1, donc nous n'ajoutons pas plus d'inclusions.
Cela signifie "imprimer la ligne" - une action vide par défaut imprime $ 0, qui imprime la ligne entière. Un liner et plus facile à comprendre que sed IMO :-)
la source
awk '/version/ && !done {print " \"version\": \"'${NEWVERSION}'\""; done=1;}; 1;' package.json
awk '/#include/ && !done { gsub(/#include/, "include \"newfile.h\""); done=1}; 1' file.c
Une collection assez complète de réponses sur la FAQ linuxtopia sed . Il souligne également que certaines réponses fournies par les personnes ne fonctionneront pas avec la version non GNU de sed, par exemple
en version non GNU devra être
Cependant, cette version ne fonctionnera pas avec gnu sed.
Voici une version qui fonctionne avec les deux:
ex:
la source
Fonctionnement de ce script: pour les lignes comprises entre 1 et la première
#include
(après la ligne 1), si la ligne commence par#include
, ajoutez la ligne spécifiée au début .Cependant, si le premier
#include
est dans la ligne 1, la ligne 1 et la suivante suivante#include
auront la ligne ajoutée. Si vous utilisez GNUsed
, il a une extension où0,/^#include/
(au lieu de1,
) fera la bonne chose.la source
Ajoutez simplement le nombre d'occurrences à la fin:
la source
sed
spécifie la commande de substitution avec:[2addr]s/BRE/replacement/flags
et note que "La valeur des drapeaux doit être zéro ou plus de: n Ne remplace la nième occurrence que du BRE trouvé dans l'espace de modèle." Ainsi, au moins dans POSIX 2008, la fin1
n'est pas unesed
extension GNU . En effet, même dans la norme SUS / POSIX 1997 , cela était pris en charge, donc j'étais très mal aligné en 2008.Une solution possible:
Explication:
la source
sed: file me4.sed line 4: ":" lacks a label
Je sais que c'est un ancien poste mais j'avais une solution que j'utilisais:
Utilisez essentiellement grep pour imprimer la première occurrence et arrêtez-vous là. En outre, imprimer le numéro de ligne, par exemple
5:line
. Canalisez cela dans sed et supprimez le: et tout ce qui se trouve après pour que vous vous retrouviez avec un numéro de ligne. Canalisez cela dans sed qui ajoute s /.*/ replace au numéro de fin, ce qui entraîne un script d'une ligne qui est canalisé dans le dernier sed pour s'exécuter en tant que script dans le fichier.donc si regex =
#include
et replace =blah
et que la première occurrence de grep est sur la ligne 5, alors les données acheminées vers le dernier sed seraient5s/.*/blah/
.Fonctionne même si la première occurrence se trouve sur la première ligne.
la source
sed -f -
qui ne le sont pas, mais vous pouvez contourner ceSi quelqu'un est venu ici pour remplacer un caractère pour la première occurrence dans toutes les lignes (comme moi), utilisez ceci:
En changeant 1 à 2 par exemple, vous pouvez remplacer uniquement tous les seconds a.
la source
's/a/b/'
signifiematch a
, etdo just first match
for every matching line
Avec l'
-z
option de GNU sed, vous pouvez traiter l'intégralité du fichier comme s'il ne s'agissait que d'une seule ligne. De cette façon, as/…/…/
ne remplacerait que la première correspondance de l'ensemble du fichier. Rappelez-vous:s/…/…/
ne remplace que la première correspondance de chaque ligne, mais avec l'-z
optionsed
traite le fichier entier comme une seule ligne.Dans le cas général, vous devez réécrire votre expression sed car l'espace de motif contient maintenant le fichier entier au lieu d'une seule ligne. Quelques exemples:
s/text.*//
peut être réécrit ens/text[^\n]*//
.[^\n]
correspond à tout sauf au caractère de nouvelle ligne.[^\n]*
correspondra à tous les symboles aprèstext
jusqu'à ce qu'une nouvelle ligne soit atteinte.s/^text//
peut être réécrit ens/(^|\n)text//
.s/text$//
peut être réécrit ens/text(\n|$)//
.la source
je le ferais avec un script awk:
puis lancez-le avec awk:
pourrait être bâclé, je suis nouveau dans ce domaine.
la source
Comme suggestion alternative, vous voudrez peut-être regarder la
ed
commande.la source
J'ai finalement réussi à faire fonctionner cela dans un script Bash utilisé pour insérer un horodatage unique dans chaque élément d'un flux RSS:
Il modifie uniquement la première occurrence.
${nowms}
est le temps en millisecondes défini par un script Perl,$counter
est un compteur utilisé pour le contrôle de boucle dans le script,\
permet de poursuivre la commande sur la ligne suivante.Le fichier est lu et stdout est redirigé vers un fichier de travail.
La façon dont je le comprends,
1,/====RSSpermalink====/
indique à sed quand s'arrêter en définissant une limitation de plage, puiss/====RSSpermalink====/${nowms}/
est la commande familière sed pour remplacer la première chaîne par la seconde.Dans mon cas, j'ai mis la commande entre guillemets doubles car je l'utilise dans un script Bash avec des variables.
la source
Utiliser FreeBSD
ed
et évitered
l'erreur "pas de correspondance" au cas où il n'y aurait pas deinclude
déclaration dans un fichier à traiter:la source
Cela pourrait fonctionner pour vous (GNU sed):
ou si la mémoire n'est pas un problème:
la source
La commande suivante supprime la première occurrence d'une chaîne, dans un fichier. Il supprime également la ligne vide. Il est présenté sur un fichier xml, mais il fonctionnerait avec n'importe quel fichier.
Utile si vous travaillez avec des fichiers xml et que vous souhaitez supprimer une balise. Dans cet exemple, il supprime la première occurrence de la balise "isTag".
Commander:
Fichier source (source.txt)
Fichier de résultats (output.txt)
ps: cela ne fonctionnait pas pour moi sur Solaris SunOS 5.10 (assez ancien), mais cela fonctionne sur Linux 2.6, sed version 4.1.5
la source
sed
(donc cela ne fonctionnait pas avec Solaris). Vous devriez supprimer ceci, s'il vous plaît - il ne fournit vraiment pas de nouvelles informations distinctives à une question qui avait déjà 4 ans et demi lorsque vous avez répondu. Certes, il a un exemple concret, mais c'est d'une valeur discutable lorsque la question a autant de réponses que celle-ci.Rien de nouveau mais peut-être une réponse un peu plus concrète:
sed -rn '0,/foo(bar).*/ s%%\1%p'
Exemple:
xwininfo -name unity-launcher
produit une sortie comme:Extraire l'ID de fenêtre avec
xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p'
produit:la source
POSIXly (également valable dans sed), une seule expression régulière utilisée, ne nécessite de la mémoire que pour une ligne (comme d'habitude):
Expliqué:
la source
Le cas d'utilisation peut peut-être être que vos occurrences sont réparties dans tout votre fichier, mais vous savez que votre seule préoccupation est dans les 10, 20 ou 100 premières lignes.
Le simple fait d'adresser ces lignes résout le problème - même si le libellé du PO ne concerne que le premier.
la source
Une solution possible ici pourrait être de dire au compilateur d'inclure l'en-tête sans qu'il soit mentionné dans les fichiers source. DANS GCC, il y a ces options:
Le compilateur de Microsoft a l' option / FI (inclusion forcée).
Cette fonctionnalité peut être pratique pour certains en-têtes communs, comme la configuration de la plate-forme. Le Makefile du noyau Linux utilise
-include
pour cela.la source
la source