Ta utgangspunkt i følgende skript:
#!/bin/sh
sed 's/(127\.0\.1\.1)\s/\1/' [some file]
Hvis jeg prøver å kjøre dette i sh
(dash
her), vil det mislykkes på grunn av parentesene, som må escapes. Men jeg trenger ikke å unnslippe selve backslashene (mellom oktettene, eller i \s
eller \1
). Hva er regelen her? Hva med når jeg må bruke {...}
eller [...]
? Finnes det en liste over hva jeg må og ikke må escape?
Det er to tolkningsnivåer her: skallet og sed.
I skallet tolkes alt mellom enkeltanførselstegn bokstavelig, bortsett fra enkeltanførselstegn i seg selv. Du kan faktisk ha et enkelt anførselstegn mellom enkelt anførselstegn ved å skrive '\''
(lukket enkelt anførselstegn, ett bokstavelig enkelt anførselstegn, åpent enkelt anførselstegn).
Sed bruker basic regular expressions. For at tegnene $.*[\^^
skal behandles bokstavelig i en BRE, må de siteres ved å sette en backslash foran, unntatt i tegnsett ([...]
). Bokstaver, sifre og (){}+?|
skal ikke siteres (i noen implementasjoner kan du slippe unna med å sitere noen av disse). Sekvensene \(
, \)
, \n
, og i noen implementasjoner \{
, \}
, \+
, \?
, \|
og andre backslash+alfanumeriske tegn har spesielle betydninger. I noen implementasjoner kan du slippe unna med å ikke sette $^
i anførselstegn i enkelte posisjoner.
Videre må du ha en backslash foran /
hvis det skal forekomme i regex utenfor parentesuttrykk. Du kan velge et alternativt tegn som skilletegn ved å skrive f.eks. s~/dir~/replacement~
eller \~/dir~p
; du trenger en backslash før skilletegnet hvis du vil ha det med i BRE. Hvis du velger et tegn som har en spesiell betydning i en BRE og du vil inkludere det bokstavelig, trenger du tre backslash; jeg anbefaler ikke dette, da det kan oppføre seg annerledes i noen implementasjoner.
Kort sagt, for sed 's/.../.../.../'
:
'\'''
for å ende opp med et enkelt anførselstegn i regexen.$.*/[\]^
og bare disse tegnene (men ikke inne i parentesuttrykk). (Teknisk sett bør du ikke sette en backslash før ]
, men jeg kjenner ikke til noen implementering som behandler ]
og \]
forskjellig utenfor parentesuttrykk).-
skal behandles bokstavelig, må du sørge for at det står først eller sist ([abc-]
eller [-abc]
, ikke [a-bc]
^
skal behandles bokstavelig inne i et parentesuttrykk, må du sørge for at det står not først (bruk [abc^]
, ikke [^abc]
]
i listen over tegn som matches av et parentesuttrykk, gjør du det til det første tegnet (eller det første etter ^
for et negert sett): []abc]
eller [^]abc]
(ikke [abc]]
eller [abc\]]
I erstatningsteksten:
&
og \
må siteres ved å sette en backslash foran dem,
det samme gjelder skilletegn (vanligvis /
) og nye linjer.\
etterfulgt av et siffer har en spesiell betydning. \
etterfulgt av en bokstav har en spesiell betydning (spesialtegn) i noen implementasjoner, og \
etterfulgt av et annet tegn betyr \c
eller c
avhengig av implementasjonen.sed 's/.../.../'
), bruk '\''
for å sette et enkelt anførselstegn i erstatningsteksten.Hvis regex- eller erstatningsteksten kommer fra en shell-variabel, må du huske at
\n
(som aldri vil matche med mindre du har annen sed
-kode som legger til nye linjetegn i mønsterområdet). Men vær oppmerksom på at det ikke vil fungere i parentesuttrykk med noen sed
-implementeringer.&
, \
og nye linjer være i anførselstegn.sed -e "s/$BRE/$REPL/"
.Problemet du opplever skyldes ikke shell-interpolering og escapes - det skyldes at du forsøker å bruke utvidet syntaks for regulære uttrykk uten å bruke -r
eller --regexp-extended
.
Endre sed-linjen fra
sed 's/(127\.0\.1\.1)\s/\1/' [some file]
til
sed -r 's/(127\.0\.1\.1)\s/\1/' [some file]
og det vil fungere slik jeg tror du har tenkt.
Som standard bruker sed grunnleggende regulære uttrykk (tenk grep-stil), som krever følgende syntaks:
sed 's/\(127\.0\.1\.1\)[ \t]/\1/' [some file]
Med mindre du ønsker å interpolere en shell-variabel inn i sed-uttrykket, bør du bruke enkle anførselstegn for hele uttrykket fordi de fører til at alt mellom dem tolkes som det er, inkludert backslashes.
Så hvis du vil at sed skal se s/\(127\.0\.1\.1\)\s/\1/
, setter du enkle anførselstegn rundt uttrykket, og skallet vil ikke røre parentesene eller backslashene i uttrykket. Hvis du må interpolere en skallvariabel, setter du bare den delen i doble anførselstegn. F.eks.
sed 's/\(127\.0\.1\.1\)/'"$ip"'/'
På denne måten slipper du å huske hvilke shell-metategn som ikke er omsluttet av doble anførselstegn.