it-swarm-eu.dev

Jak použít sed nahradit pouze první výskyt v souboru?

Chtěl bych aktualizovat velký počet zdrojových souborů C + + s extra směrnicí ještě před existujícím #includes. Pro tento druh úkolu normálně používám malý bash skript se sedem pro přepsání souboru. 

Jak mohu získat sed nahradit pouze první výskyt řetězce v souboru, nikoli nahradit každý výskyt?

Pokud používám

sed s/#include/#include "newfile.h"\n#include/

nahrazuje všechny #includes. 

Vítány jsou i alternativní návrhy na dosažení stejné věci.

173
David Dibben
 # sed script to change "foo" to "bar" only on the first occurrence
 1{x;s/^/first/;x;}
 1,/foo/{x;/first/s///;x;s/foo/bar/;}
 #---end of script---

nebo, pokud dáváte přednost: Poznámka editora: funguje pouze s GNUsed.

sed '0,/RE/s//to_that/' file 

Zdroj

108
Ben Hoffstein

Napište sed skript, který nahradí pouze první výskyt "Apple" podle "Banana"

Příklad vstupu: Výstup:

     Apple       Banana
     Orange      Orange
     Apple       Apple

Toto je jednoduchý skript: Poznámka editora: pracuje pouze sGNUsed.

sed '0,/Apple/{s/Apple/Banana/}' filename
231
tim
sed '0,/pattern/s/pattern/replacement/' filename

to fungovalo pro mě.

příklad

sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt

Poznámka editora: obě pracují pouze sGNUsed.

50
Sushil

Můžete použít awk udělat něco podobného.

awk '/#include/ && !done { print "#include \"newfile.h\""; done=1;}; 1;' file.c

Vysvětlení:

/#include/ && !done

Spustí příkaz akce mezi {}, když řádek odpovídá "#include" a my jsme ho ještě nezpracovali.

{print "#include \"newfile.h\""; done=1;}

Tento výtisk #include "newfile.h", musíme uniknout uvozovek. Pak nastavíme nastavenou proměnnou na 1, takže další položky nepřidáváme.

1;

To znamená "vytisknout řádek" - výchozí prázdná akce pro tisk $ 0, která vytiskne celý řádek. Jedna vložka a snazší porozumění než sed IMO :-)

22
richq

Docela komplexní kolekce odpovědí na linuxtopia sed FAQ . Zdůrazňuje také, že některé odpovědi, které lidé poskytli, nebudou fungovat s verzí, kterou nemá GNU verze, např 

sed '0,/RE/s//to_that/' file

v non-GNU verzi 

sed -e '1s/RE/to_that/;t' -e '1,/RE/s//to_that/'

Tato verze však nebude fungovat s gnu sed. 

Zde je verze, která funguje s oběma možnostmi:

-e '/RE/{s//to_that/;:a' -e '$!N;$!ba' -e '}'

ex:

sed -e '/Apple/{s//Banana/;:a' -e '$!N;$!ba' -e '}' filename
16
MikhailVS
#!/bin/sed -f
1,/^#include/ {
    /^#include/i\
#include "newfile.h"
}

Jak tento skript funguje: Pro řádky mezi 1 a prvním kódem #include (za řádkem 1), pokud řádek začíná znakem #include, pak předepište zadaný řádek.

Pokud je však první řádek #include v řádku 1, pak řádek 1 a následující následující #include bude mít řádek předponu. Pokud používáte GNU sed, má příponu, kde 0,/^#include/ (místo 1,) udělá správnou věc.

12

Stačí přidat počet výskytů na konci:

sed s/#include/#include "newfile.h"\n#include/1
11
unexist

Možné řešení:

    /#include/!{p;d;}
    i\
    #include "newfile.h"
    :
    n
    b

Vysvětlení:

  • čte řádky, dokud nenajdeme #include, vytiskneme tyto řádky a pak začneme nový cyklus
  • vložte nový řádek zahrnutí
  • zadejte smyčku, která právě čte řádky (ve výchozím nastavení budou tyto řádky také tisknuty), se odtud nedostaneme zpět do první části skriptu
8
mitchnull

Vím, že je to starý příspěvek, ale měl jsem řešení, které jsem používal:

grep -E -m 1 -n 'old' file | sed 's/:.*$//' - | sed 's/$/s\/old\/new\//' - | sed -f - file

V podstatě použijte grep, abyste našli první výskyt a zastavili se tam. Vytiskněte také číslo řádku, tj. 5: řádek. Potrubí, které do sed a odstranit: a cokoliv po, takže jste právě vlevo s číslem řádku. Potrubí, které do sed, které přidá s /.*/ nahradit na konec, který dává 1 řádek skript, který je piped do posledního sed spustit jako skript na soubor.

tak jestliže regex = #include a replace = blah a první výskyt grep nálezů je na řádku 5 pak data piped k poslední sed by byla 5s /.*/ blah /.

3
Michael Edwards

Pokud sem přišel někdo nahradit znak pro první výskyt ve všech řádcích (jako já), použijte toto:

sed '/old/s/old/new/1' file

-bash-4.2$ cat file
123a456a789a
12a34a56
a12
-bash-4.2$ sed '/a/s/a/b/1' file
123b456a789a
12b34a56
b12

Například změnou 1 na 2 můžete místo toho nahradit všechny druhé a.

2
FatihSarigol

Jako alternativní návrh se můžete podívat na příkaz ed.

man 1 ed

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# for in-place file editing use "ed -s file" and replace ",p" with "w"
# cf. http://wiki.bash-hackers.org/howto/edit-ed
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   /# *include/i
   #include "newfile.h"
   .
   ,p
   q
EOF
2
timo

Použití FreeBSD ed a vyhnout se chybě ed "no match" v případě, že v souboru, který má být zpracován, neexistuje žádný příkaz include

teststr='
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
'

# using FreeBSD ed
# to avoid ed's "no match" error, see
# *emphasized text*http://codesnippets.joyent.com/posts/show/11917 
cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s <(echo "$teststr")
   H
   ,g/# *include/u\
   u\
   i\
   #include "newfile.h"\
   .
   ,p
   q
EOF
2
nazq

Nakonec jsem to dostal do práce s Bash skriptem, který se používá pro vložení jedinečné časové značky do každé položky v RSS kanálu:

        sed "1,/====RSSpermalink====/s/====RSSpermalink====/${nowms}/" \
            production-feed2.xml.tmp2 > production-feed2.xml.tmp.$counter

Změní pouze první výskyt. 

${nowms} je čas v milisekundách nastavený skriptem Perl, $counter je čítač používaný pro řízení smyčky ve skriptu, \ umožňuje pokračování příkazu na dalším řádku.

Soubor je načten a stdout je přesměrován do pracovního souboru.

Jak to chápu, 1,/====RSSpermalink====/ řekne sed, kdy zastavit nastavením omezení rozsahu, a pak s/====RSSpermalink====/${nowms}/ je známý příkaz sed, který nahradí první řetězec druhým. 

V mém případě jsem dal příkaz do dvojitých uvozovek becauase, které používám ve skriptu Bash s proměnnými.

2
Michael Cook

To by mohlo fungovat pro vás (GNU sed):

sed -si '/#include/{s//& "newfile.h\n&/;:a;$!{n;ba}}' file1 file2 file....

nebo pokud paměť není problém:

sed -si ':a;$!{N;ba};s/#include/& "newfile.h\n&/' file1 file2 file...
2
potong

já bych to dělal s awk skriptem:

BEGIN {i=0}
(i==0) && /#include/ {print "#include \"newfile.h\""; i=1}
{print $0}    
END {}

pak ho spusťte s awk:

awk -f awkscript headerfile.h > headerfilenew.h

může být nedbalý, jsem k tomu nový.

2
wakingrufus

Nic nového, ale možná poněkud konkrétnější odpověď: sed -rn '0,/foo(bar).*/ s%%\1%p'

Příklad: xwininfo -name unity-launcher produkuje výstup jako:

xwininfo: Window id: 0x2200003 "unity-launcher"

  Absolute upper-left X:  -2980
  Absolute upper-left Y:  -198
  Relative upper-left X:  0
  Relative upper-left Y:  0
  Width: 2880
  Height: 98
  Depth: 24
  Visual: 0x21
  Visual Class: TrueColor
  Border width: 0
  Class: InputOutput
  Colormap: 0x20 (installed)
  Bit Gravity State: ForgetGravity
  Window Gravity State: NorthWestGravity
  Backing Store State: NotUseful
  Save Under State: no
  Map State: IsViewable
  Override Redirect State: no
  Corners:  +-2980+-198  -2980+-198  -2980-1900  +-2980-1900
  -geometry 2880x98+-2980+-198

ID výpisu pomocí xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p' vytvoří:

0x2200003
0

POSIXly (platí také v sed), Pouze jeden regex použitý, potřebují paměť pouze pro jeden řádek (jako obvykle):

sed '/\(#include\).*/!b;//{h;s//\1 "newfile.h"/;G};:1;n;b1'

Vysvětleno:

sed '
/\(#include\).*/!b          # Only one regex used. On lines not matching
                            # the text  `#include` **yet**,
                            # branch to end, cause the default print. Re-start.
//{                         # On first line matching previous regex.
    h                       # hold the line.
    s//\1 "newfile.h"/      # append ` "newfile.h"` to the `#include` matched.
    G                       # append a newline.
  }                         # end of replacement.
:1                          # Once **one** replacement got done (the first match)
n                           # Loop continually reading a line each time
b1                          # and printing it by default.
'                           # end of sed script.
0
Isaac

S volbou GNU sed's -z můžete zpracovat celý soubor, jako by to byl pouze jeden řádek. Tímto způsobem s/…/…/ nahradí pouze první zápas v celém souboru. Nezapomeňte: s/…/…/ nahrazuje pouze první shodu v každém řádku, ale volba -zsed zachází s celým souborem jako s jedním řádkem.

sed -z 's/#include/#include "newfile.h"\n#include'

V obecném případě musíte přepsat svůj výraz, protože místo vzoru nyní drží celý soubor namísto jednoho řádku. Nějaké příklady:

  • s/text.*// lze přepsat jako s/text[^\n]*//. [^\n] odpovídá všemu kromě znaku nového řádku. [^\n]* bude odpovídat všem symbolům po text až do dosažení nového řádku.
  • s/^text// lze přepsat jako s/(^|\n)text//.
  • s/text$// lze přepsat jako s/text(\n|$)//.
0
Socowi

Následující příkaz odstraní první výskyt řetězce v rámci souboru. Odstraní také prázdný řádek. Je prezentován na souboru XML, ale bude pracovat s libovolným souborem. 

Užitečné, pokud pracujete s xml soubory a chcete značku odstranit. V tomto příkladu odstraní první výskyt značky "isTag".

Příkaz: 

sed -e 0,/'<isTag>false<\/isTag>'/{s/'<isTag>false<\/isTag>'//}  -e 's/ *$//' -e  '/^$/d'  source.txt > output.txt

Zdrojový soubor (source.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <isTag>false</isTag>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

Soubor výsledků (output.txt)

<xml>
    <testdata>
        <canUseUpdate>true</canUseUpdate>
        <moduleLocations>
            <module>esa_jee6</module>
            <isTag>false</isTag>
        </moduleLocations>
        <node>
            <isTag>false</isTag>
        </node>
    </testdata>
</xml>

ps: nepracoval pro mě na Solaris SunOS 5.10 (docela starý), ale funguje na Linuxu 2.6, verze sed 4.1.1