Je souhaite remplacer uniquement les premières instances k
d'un mot.
Comment puis-je faire ceci?
Par exemple. Dire le fichier foo.txt
contient 100 occurrences d'occurrences de Word 'linux'.
Je dois remplacer seulement les 50 premières occurrences.
La première section ci-dessous décrit l'utilisation de sed
pour changer les premières occurrences k sur une ligne. La deuxième section étend cette approche pour modifier uniquement les premières occurrences k d'un fichier, quelle que soit la ligne sur laquelle elles apparaissent.
Avec sed standard, il existe une commande pour remplacer la k-ème occurrence d'un mot sur une ligne. Si k
vaut 3, par exemple:
sed 's/old/new/3'
Ou, on peut remplacer toutes les occurrences par:
sed 's/old/new/g'
Ni l'un ni l'autre n'est ce que vous voulez.
GNU sed
propose une extension qui changera la kième occurrence et tout cela après. Si k est 3, par exemple:
sed 's/old/new/g3'
Ceux-ci peuvent être combinés pour faire ce que vous voulez. Pour modifier les 3 premières occurrences:
$ echo old old old old old | sed -E 's/\<old\>/\n/g4; s/\<old\>/new/g; s/\n/old/g'
new new new old old
où \n
est utile ici car nous pouvons être sûrs qu'il ne se produit jamais sur une ligne.
Nous utilisons trois commandes de substitution sed
:
s/\<old\>/\n/g4
Il s'agit de l'extension GNU pour remplacer la quatrième occurrence et toutes les occurrences suivantes de old
par \n
.
La fonction regex étendue \<
est utilisé pour faire correspondre le début d'un mot et \>
pour correspondre à la fin d'un mot. Cela garantit que seuls les mots complets correspondent. L'expression regex étendue nécessite le -E
option pour sed
.
s/\<old\>/new/g
Seules les trois premières occurrences de old
restent et cela les remplace toutes par new
.
s/\n/old/g
La quatrième et toutes les occurrences restantes de old
ont été remplacées par \n
dans la première étape. Cela les ramène à leur état d'origine.
Si GNU sed n'est pas disponible et que vous souhaitez modifier les 3 premières occurrences de old
en new
, utilisez alors trois commandes s
:
$ echo old old old old old | sed -E -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
new new new old old
Cela fonctionne bien lorsque k
est un petit nombre mais évolue mal en grand k
.
Étant donné que certains seds non GNU ne prennent pas en charge la combinaison de commandes avec des points-virgules, chaque commande ici est introduite avec son propre -e
option. Il peut également être nécessaire de vérifier que votre sed
prend en charge les symboles de limite Word, \<
et \>
.
Nous pouvons dire à sed de lire l'intégralité du fichier puis d'effectuer les substitutions. Par exemple, pour remplacer les trois premières occurrences de old
à l'aide d'un sed de style BSD:
sed -E -e 'H;1h;$!d;x' -e 's/\<old\>/new/' -e 's/\<old\>/new/' -e 's/\<old\>/new/'
Les commandes sed H;1h;$!d;x
lire l'intégralité du fichier.
Parce que ce qui précède n'utilise aucune extension GNU, il devrait fonctionner sur BSD (OSX) sed. Notez, pensons, que cette approche nécessite un sed
qui peut gérer les longues lignes. GNU sed
devrait convenir. Ceux qui utilisent une version non GNU de sed
devraient tester sa capacité à gérer les longues lignes.
Avec un GNU sed, nous pouvons continuer à utiliser l'astuce g
décrite ci-dessus, mais avec \n
remplacé par \x00
, pour remplacer les trois premières occurrences:
sed -E -e 'H;1h;$!d;x; s/\<old\>/\x00/g4; s/\<old\>/new/g; s/\x00/old/g'
Cette approche évolue bien lorsque k
devient grand. Cela suppose, cependant, que \x00
n'est pas dans votre chaîne d'origine. Puisqu'il est impossible de mettre le caractère \x00
dans une chaîne bash, il s'agit généralement d'une hypothèse sûre.
Les commandes awk peuvent être utilisées pour remplacer les N premières occurrences de Word par le remplacement.
Les commandes ne seront remplacées que si le mot est une correspondance complète.
Dans les exemples ci-dessous, je remplace le premier 27
occurrences de old
avec new
en utilisant sub
awk '{for(i=1;i<=NF;i++){if(x<27&&$i=="old"){x++;sub("old","new",$i)}}}1' file
Cette commande parcourt chaque champ jusqu'à ce qu'elle corresponde à
old
, elle vérifie que le compteur est inférieur à 27, incrémente et substitue la première correspondance sur la ligne. Se déplace ensuite sur le champ/ligne suivant et se répète.
Remplacement du champ manuellement
awk '{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
Similaire à la commande précédente, mais comme elle a déjà un marqueur sur quel champ, elle dépend de
($i)
, il change simplement la valeur du champ deold
ànew
.
Effectuer une vérification avant
awk '/old/&&x<27{for(i=1;i<=NF;i++)if(x<27&&$i=="old"&&$i="new")x++}1' file
Vérifier que la ligne contient old et que le compteur est inférieur à 27
SHOULD
fournit une petite augmentation de vitesse car elle ne traitera pas les lignes lorsqu'elles sont fausses.
RÉSULTATS
Par exemple
old bold old old old
old old nold old old
old old old gold old
old gold gold old old
old old old man old old
old old old old dog old
old old old old say old
old old old old blah old
à
new bold new new new
new new nold new new
new new new gold new
new gold gold new new
new new new man new new
new new new new dog new
new new old old say old
old old old old blah old
Supposons que vous souhaitiez remplacer uniquement les trois premières instances d'une chaîne ...
seq 11 100 311 |
sed -e 's/1/\
&/g' \ #s/match string/\nmatch string/globally
-e :t \ #define label t
-e '/\n/{ x' \ #newlines must match - exchange hold and pattern spaces
-e '/.\{3\}/!{' \ #if not 3 characters in hold space do
-e 's/$/./' \ #add a new char to hold space
-e x \ #exchange hold/pattern spaces again
-e 's/\n1/2/' \ #replace first occurring '\n1' string w/ '2' string
-e 'b t' \ #branch back to label t
-e '};x' \ #end match function; exchange hold/pattern spaces
-e '};s/\n//g' #end match function; remove all newline characters
note: ce qui précède ne fonctionnera probablement pas avec les commentaires intégrés
... ou dans mon cas d'exemple, d'un '1' ...
22
211
211
311
Là, j'utilise deux techniques notables. En premier lieu, chaque occurrence de 1
sur une ligne est remplacé par \n1
. De cette façon, comme je fais les remplacements récursifs ensuite, je peux être sûr de ne pas remplacer l'occurrence deux fois si ma chaîne de remplacement contient ma chaîne de remplacement. Par exemple, si je remplace he
par hey
cela fonctionnera toujours.
Je fais ça comme:
s/1/\
&/g
Deuxièmement, je compte les remplacements en ajoutant un caractère à h
old espace pour chaque occurrence. Une fois que j'aurai atteint trois, il ne se passera plus. Si vous appliquez cela à vos données et modifiez le \{3\}
au nombre total de remplacements que vous désirez et le /\n1/
adresses à tout ce que vous voulez remplacer, vous ne devez en remplacer que autant que vous le souhaitez.
Je n'ai fait que -e
des trucs pour plus de lisibilité. POSIX Il pourrait être écrit comme ceci:
nl='
'; sed "s/1/\\$nl&/g;:t${nl}/\n/{x;/.\{3\}/!{${nl}s/$/./;x;s/\n1/2/;bt$nl};x$nl};s/\n//g"
Et w/GNU sed
:
sed 's/1/\n&/g;:t;/\n/{x;/.\{3\}/!{s/$/./;x;s/\n1/2/;bt};x};s/\n//g'
Rappelez-vous également que sed
est orienté ligne - il ne lit pas dans tout le fichier et essaie ensuite de le boucler comme c'est souvent le cas dans d'autres éditeurs. sed
est simple et efficace. Cela dit, il est souvent pratique de faire quelque chose comme ceci:
Voici une petite fonction Shell qui l'intègre dans une commande simplement exécutée:
firstn() { sed "s/$2/\
&/g;:t
/\n/{x
/.\{$(($1))"',\}/!{
s/$/./; x; s/\n'"$2/$3"'/
b t
};x
};s/\n//g'; }
Donc avec ça je peux faire:
seq 11 100 311 | firstn 7 1 5
...et obtenir...
55
555
255
311
...ou...
seq 10 1 25 | firstn 6 '\(.\)\([1-5]\)' '\15\2'
...obtenir...
10
151
152
153
154
155
16
17
18
19
20
251
22
23
24
25
... ou, pour correspondre à votre exemple (sur un ordre de grandeur plus petit):
yes linux | head -n 10 | firstn 5 linux 'linux is an os kernel'
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux is an os kernel
linux
linux
linux
linux
linux
Une courte alternative en Perl:
Perl -pe 'BEGIN{$n=3} 1 while s/old/new/ && ++$i < $n' your_file
Modifiez la valeur de `$ n $ à votre guise.
Comment ça marche:
new
par old
(s/old/new/
) et chaque fois qu'il le peut, il incrémente la variable $i
(++$i
).1 while ...
) tant qu'il a fait moins de $n
substitutions au total et il peut effectuer au moins une substitution sur cette ligne.Utilisez une boucle Shell et ex
!
{ for i in {1..50}; do printf %s\\n '0/old/s//new/'; done; echo x;} | ex file.txt
Oui, c'est un peu maladroit.
;)
Remarque: cela peut échouer s'il y a moins de 50 instances de old
dans le fichier. (Je ne l'ai pas testé.) Si c'est le cas, le fichier ne sera pas modifié.
Mieux encore, utilisez Vim.
vim file.txt
qqgg/old<CR>:s/old/new/<CR>q49@q
:x
Explication:
q # Start recording macro
q # Into register q
gg # Go to start of file
/old<CR> # Go to first instance of 'old'
:s/old/new/<CR> # Change it to 'new'
q # Stop recording
49@q # Replay macro 49 times
:x # Save and exit
Une solution simple, mais pas très rapide, consiste à parcourir les commandes décrites dans https://stackoverflow.com/questions/148451/how-to-use-sed-to-replace-only-the-first-occurrence -dans un fichier
for i in $(seq 50) ; do sed -i -e "0,/oldword/s//newword/" file.txt ; done
Cette commande sed particulière ne fonctionne probablement que pour GNU sed et si newword ne fait pas partie de oldword . Pour les non-GNU sed voir ici comment remplacer uniquement le premier motif d'un fichier.
Avec GNU awk
, vous pouvez définir le séparateur d'enregistrement RS
sur Word à remplacer délimité par les limites de Word. Ensuite, il est un cas de définition du séparateur d'enregistrement sur la sortie sur le mot de remplacement pour les premiers enregistrements k
tout en conservant le séparateur d'enregistrement d'origine pour le reste
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, NR <= limit? replacement: RT}' file
OR
awk -vRS='\\ylinux\\y' -vreplacement=unix -vlimit=50 \
'{printf "%s%s", $0, limit--? replacement: RT}' file