diff --git a/README.md b/README.md
index 8aa2744..a8f5c2c 100644
--- a/README.md
+++ b/README.md
@@ -56,14 +56,16 @@ func main() {
|__Expression__ | __Meaning__ |
| ----------------- | -------------- |
-|`${var}` | Value of var (same as `$var`)
+|`${var}` | Value of var (same as `$var`)
|`${var-$DEFAULT}` | If var not set, evaluate expression as $DEFAULT
|`${var:-$DEFAULT}` | If var not set or is empty, evaluate expression as $DEFAULT
|`${var=$DEFAULT}` | If var not set, evaluate expression as $DEFAULT
|`${var:=$DEFAULT}` | If var not set or is empty, evaluate expression as $DEFAULT
-|`${var+$OTHER}` | If var set, evaluate expression as $OTHER, otherwise as empty string
+|`${var+$OTHER}` | If var set, evaluate expression as $OTHER, otherwise as empty string
|`${var:+$OTHER}` | If var set, evaluate expression as $OTHER, otherwise as empty string
-table taken from [here](http://www.tldp.org/LDP/abs/html/refcards.html#AEN22728)
+|`$$var` | Escape expressions. Result will be `$var`.
+
+Most of the rows in this table were taken from [here](http://www.tldp.org/LDP/abs/html/refcards.html#AEN22728)
### See also
diff --git a/parse/lex.go b/parse/lex.go
index 49236b7..f7fd4df 100644
--- a/parse/lex.go
+++ b/parse/lex.go
@@ -144,17 +144,24 @@ Loop:
switch r := l.next(); r {
case '$':
l.pos--
+ // emit the text we've found until here, if any.
if l.pos > l.start {
l.emit(itemText)
}
l.pos++
- if r := l.next(); isAlphaNumeric(r) {
- l.backup()
- return lexVariable
- } else if r == '{' {
+ switch r := l.peek(); {
+ case r == '$':
+ // ignore the previous '$'.
+ l.ignore()
+ l.next()
+ l.emit(itemText)
+ case r == '{':
+ l.next()
l.subsDepth++
l.emit(itemLeftDelim)
return lexSubstitution
+ case isAlphaNumeric(r):
+ return lexVariable
}
case eof:
break Loop
diff --git a/parse/lex_test.go b/parse/lex_test.go
index 080fa82..b8f8ed5 100644
--- a/parse/lex_test.go
+++ b/parse/lex_test.go
@@ -81,13 +81,25 @@ var lexTests = []lexTest{
{itemVariable, 0, "world"},
{itemError, 0, "closing brace expected"},
}},
+ {"escaping $$var", "hello $$HOME", []item{
+ {itemText, 0, "hello "},
+ {itemText, 7, "$"},
+ {itemText, 8, "HOME"},
+ tEOF,
+ }},
+ {"escaping $${subst}", "hello $${HOME}", []item{
+ {itemText, 0, "hello "},
+ {itemText, 7, "$"},
+ {itemText, 8, "{HOME}"},
+ tEOF,
+ }},
}
func TestLex(t *testing.T) {
for _, test := range lexTests {
items := collect(&test)
if !equal(items, test.items, false) {
- t.Errorf("%s: got\n\t%+v\nexpected\n\t%v", test.name, items, test.items)
+ t.Errorf("%s:\ninput\n\t%q\ngot\n\t%+v\nexpected\n\t%v", test.name, test.input, items, test.items)
}
}
}
diff --git a/parse/parse_test.go b/parse/parse_test.go
index 7152317..af6f498 100644
--- a/parse/parse_test.go
+++ b/parse/parse_test.go
@@ -99,6 +99,12 @@ var parseTests = []parseTest{
{"$var and $DEFAULT empty :=", "${EMPTY:=$ALSO_EMPTY}", "", errEmpty},
{"$var and $OTHER empty +", "${EMPTY+$ALSO_EMPTY}", "", errEmpty},
{"$var and $OTHER empty :+", "${EMPTY:+$ALSO_EMPTY}", "", errEmpty},
+
+ // escaping.
+ {"escape $$var", "FOO $$BAR BAZ", "FOO $BAR BAZ", errNone},
+ {"escape $${subst}", "FOO $${BAR} BAZ", "FOO ${BAR} BAZ", errNone},
+ {"escape $$$var", "$$$BAR", "$bar", errNone},
+ {"escape $$${subst}", "$$${BAZ:-baz}", "$baz", errNone},
}
func TestParse(t *testing.T) {