diff --git a/src/augeas.c b/src/augeas.c
index b31560d..ba50071 100644
--- a/src/augeas.c
+++ b/src/augeas.c
@@ -1984,6 +1984,20 @@ int aug_transform(struct augeas *aug, const char *lens,
     return result;
 }
 
+int aug_escape_name(augeas *aug, const char *in, char **out) {
+    int result;
+
+    api_entry(aug);
+    ARG_CHECK(in == NULL, aug, "aug_escape_name: IN must not be NULL");
+    ARG_CHECK(out == NULL, aug, "aug_escape_name: OUT must not be NULL");
+
+    result = pathx_escape_name(in, out);
+    ERR_NOMEM(result < 0, aug);
+ error:
+    api_exit(aug);
+    return result;
+}
+
 int aug_print(const struct augeas *aug, FILE *out, const char *pathin) {
     struct pathx *p;
     int result;
diff --git a/src/augeas.h b/src/augeas.h
index b4d6003..3225651 100644
--- a/src/augeas.h
+++ b/src/augeas.h
@@ -370,6 +370,23 @@ int aug_text_retrieve(struct augeas *aug, const char *lens,
                       const char *node_in, const char *path,
                       const char *node_out);
 
+/* Function: aug_escape_name
+ *
+ * Escape special characters in a string such that it can be used as part
+ * of a path expressions and only matches a node named exactly
+ * IN. Characters that have special meanings in path expressions, such as
+ * '[' and ']' are prefixed with a '\\'. Note that this function assumes
+ * that it is passed a name, not a path, and will therefore escape '/',
+ * too.
+ *
+ * On return, *OUT is NULL if IN does not need any escaping at all, and
+ * points to an escaped copy of IN otherwise.
+ *
+ * Returns:
+ * 0 on success, or a negative value on failure
+ */
+int aug_escape_name(augeas *aug, const char *in, char **out);
+
 /* Function: aug_print
  *
  * Print each node matching PATH and its descendants to OUT.
diff --git a/src/augeas_sym.version b/src/augeas_sym.version
index 3740f67..c128e8d 100644
--- a/src/augeas_sym.version
+++ b/src/augeas_sym.version
@@ -65,3 +65,8 @@ AUGEAS_0.18.0 {
     global:
       aug_cp;
 } AUGEAS_0.16.0;
+
+AUGEAS_0.19.0 {
+    global:
+      aug_escape_name;
+} AUGEAS_0.18.0;
\ No newline at end of file
diff --git a/src/internal.c b/src/internal.c
index b9e3eab..e149fe6 100644
--- a/src/internal.c
+++ b/src/internal.c
@@ -336,6 +336,8 @@ char *path_expand(struct tree *tree, const char *ppath) {
 
     char *path;
     const char *label;
+    char *escaped = NULL;
+
     int cnt = 0, ind = 0, r;
 
     list_for_each(t, siblings) {
@@ -356,11 +358,21 @@ char *path_expand(struct tree *tree, const char *ppath) {
     else
         label = tree->label;
 
+    r = pathx_escape_name(label, &escaped);
+    if (r < 0)
+        return NULL;
+
+    if (escaped != NULL)
+        label = escaped;
+
     if (cnt > 1) {
         r = asprintf(&path, "%s/%s[%d]", ppath, label, ind);
     } else {
         r = asprintf(&path, "%s/%s", ppath, label);
     }
+
+    free(escaped);
+
     if (r == -1)
         return NULL;
     return path;
diff --git a/src/internal.h b/src/internal.h
index 0b4d5ac..85d8ee3 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -575,6 +575,16 @@ void pathx_symtab_remove_descendants(struct pathx_symtab *symtab,
                                      const struct tree *tree);
 void free_symtab(struct pathx_symtab *symtab);
 
+/* Escape a name so that it is safe to pass to parse_name and have it
+ * interpreted as the literal name of a path component.
+ *
+ * On return, *OUT will be NULL if IN does not need escaping, otherwise it
+ * will contain an escaped copy of IN which the caller must free.
+ *
+ * Returns -1 if it failed to allocate memory for *OUT, 0 on success
+ */
+int pathx_escape_name(const char *in, char **out);
+
 /* Debug helpers, all defined in internal.c. When ENABLE_DEBUG is not
  * set, they compile to nothing.
  */
diff --git a/src/pathx.c b/src/pathx.c
index 467e5d7..0c4974d 100644
--- a/src/pathx.c
+++ b/src/pathx.c
@@ -123,6 +123,15 @@ static const char *const axis_names[] = {
 
 static const char *const axis_sep = "::";
 
+/* The characters that can follow a name in a location expression (aka path)
+ * The parser will assume that name (path component) is finished when it
+ * encounters any of these characters, unless they are escaped by preceding
+ * them with a '\\'.
+ *
+ * See parse_name for the gory details
+ */
+static const char const name_follow[] = "][|/=()!,";
+
 /* Doubly linked list of location steps. Besides the information from the
  * path expression, also contains information to iterate over a node set,
  * in particular, the context node CTX for the step, and the current node
@@ -1607,17 +1616,43 @@ static void push_new_binary_op(enum binary_op op, struct state *state) {
     push_expr(expr, state);
 }
 
+int pathx_escape_name(const char *in, char **out) {
+    const char *p;
+    int num_to_escape = 0;
+    char *s;
+
+    *out = NULL;
+
+    for (p = in; *p; p++) {
+        if (strchr(name_follow, *p) || isspace(*p))
+            num_to_escape += 1;
+    }
+
+    if (num_to_escape == 0)
+        return 0;
+
+    if (ALLOC_N(*out, strlen(in) + num_to_escape + 1) < 0)
+        return -1;
+
+    for (p = in, s = *out; *p; p++) {
+        if (strchr(name_follow, *p) || isspace(*p))
+            *s++ = '\\';
+        *s++ = *p;
+    }
+    *s = '\0';
+    return 0;
+}
+
 /*
  * NameNoWS ::= [^][|/\= \t\n] | \\.
  * NameWS   ::= [^][|/\=] | \\.
  * Name ::= NameNoWS NameWS* NameNoWS | NameNoWS
  */
 static char *parse_name(struct state *state) {
-    static const char const follow[] = "][|/=()!,";
     const char *s = state->pos;
     char *result;
 
-    while (*state->pos != '\0' && strchr(follow, *state->pos) == NULL) {
+    while (*state->pos != '\0' && strchr(name_follow, *state->pos) == NULL) {
         /* This is a hack: since we allow spaces in names, we need to avoid
          * gobbling up stuff that is in follow(Name), e.g. 'or' so that
          * things like [name1 or name2] still work.
diff --git a/src/transform.c b/src/transform.c
index 1026912..7a355d2 100644
--- a/src/transform.c
+++ b/src/transform.c
@@ -1330,21 +1330,34 @@ int text_retrieve(struct augeas *aug, const char *lens_name,
 }
 
 int remove_file(struct augeas *aug, struct tree *tree) {
-    char *path = NULL;
-    const char *filename = NULL;
     const char *err_status = NULL;
     char *dyn_err_status = NULL;
     char *augsave = NULL, *augorig = NULL, *augorig_canon = NULL;
+    struct tree *path = NULL;
+    const char *file_path = NULL;
+    char *meta_path = NULL;
     int r;
 
-    path = path_of_tree(tree);
+    path = tree_child(tree, s_path);
     if (path == NULL) {
+        err_status = "no child called 'path' for file entry";
+        goto error;
+    }
+    file_path = path->value + strlen(AUGEAS_FILES_TREE);
+    path = NULL;
+
+    if (file_path == NULL) {
+        err_status = "no path for file";
+        goto error;
+    }
+
+    meta_path = path_of_tree(tree);
+    if (meta_path == NULL) {
         err_status = "path_of_tree";
         goto error;
     }
-    filename = path + strlen(AUGEAS_META_FILES);
 
-    if ((augorig = strappend(aug->root, filename + 1)) == NULL) {
+    if ((augorig = strappend(aug->root, file_path)) == NULL) {
         err_status = "root_file";
         goto error;
     }
@@ -1359,7 +1372,7 @@ int remove_file(struct augeas *aug, struct tree *tree) {
         }
     }
 
-    r = file_saved_event(aug, path + strlen(AUGEAS_META_TREE));
+    r = file_saved_event(aug, meta_path + strlen(AUGEAS_META_TREE));
     if (r < 0) {
         err_status = "saved_event";
         goto error;
@@ -1389,9 +1402,10 @@ int remove_file(struct augeas *aug, struct tree *tree) {
             goto error;
         }
     }
+    path = NULL;
     tree_unlink(aug, tree);
  done:
-    free(path);
+    free(meta_path);
     free(augorig);
     free(augorig_canon);
     free(augsave);
@@ -1400,9 +1414,9 @@ int remove_file(struct augeas *aug, struct tree *tree) {
     {
         const char *emsg =
             dyn_err_status == NULL ? err_status : dyn_err_status;
-        store_error(aug, filename, path, emsg, errno, NULL, NULL);
+        store_error(aug, file_path, meta_path, emsg, errno, NULL, NULL);
     }
-    free(path);
+    free(meta_path);
     free(augorig);
     free(augorig_canon);
     free(augsave);
diff --git a/tests/run.tests b/tests/run.tests
index 39f8153..c74b644 100644
--- a/tests/run.tests
+++ b/tests/run.tests
@@ -440,13 +440,13 @@ test set-escaped-path-bracket 2
   set /white\ space/\[section value
   print /white\ space/\[section
 prints
-  /white space/[section = "value"
+  /white\ space/\[section = "value"
 
 test set-squote-escaped-bracket 2
   set '/augeas/\[section' value
   print '/augeas/\[section'
 prints
-  /augeas/[section = "value"
+  /augeas/\[section = "value"
 
 test set-squote-escaped-path 2
   set '/white\ space' value
diff --git a/tests/test-api.c b/tests/test-api.c
index 9367c27..cc2cc87 100644
--- a/tests/test-api.c
+++ b/tests/test-api.c
@@ -625,6 +625,23 @@ static void testTextRetrieve(CuTest *tc) {
     CuAssertStrEquals(tc, hosts, hosts_out);
 }
 
+static void testAugEscape(CuTest *tc) {
+    static const char *const in  = "a/[]b|=c()!, \td";
+    static const char *const exp = "a\\/\\[\\]b\\|\\=c\\(\\)\\!\\,\\ \\\td";
+    char *out;
+    struct augeas *aug;
+    int r;
+
+    aug = aug_init(root, loadpath, AUG_NO_STDINC|AUG_NO_LOAD);
+    CuAssertPtrNotNull(tc, aug);
+
+    r = aug_escape_name(aug, in, &out);
+    CuAssertRetSuccess(tc, r);
+
+    CuAssertStrEquals(tc, out, exp);
+    free(out);
+}
+
 int main(void) {
     char *output = NULL;
     CuSuite* suite = CuSuiteNew();
@@ -643,6 +660,7 @@ int main(void) {
     SUITE_ADD_TEST(suite, testToXml);
     SUITE_ADD_TEST(suite, testTextStore);
     SUITE_ADD_TEST(suite, testTextRetrieve);
+    SUITE_ADD_TEST(suite, testAugEscape);
 
     abs_top_srcdir = getenv("abs_top_srcdir");
     if (abs_top_srcdir == NULL)
diff --git a/tests/test-save.c b/tests/test-save.c
index f28f626..2fe90a9 100644
--- a/tests/test-save.c
+++ b/tests/test-save.c
@@ -266,6 +266,58 @@ static void testUmask022(CuTest *tc) {
     testUmask(tc, 0022, 0644);
 }
 
+/* Test that handling of 'strange' characters in path names works as
+ * expected. In particular, that paths with characters that have special
+ * meaning in path expressions are escaped properly.
+ *
+ * This test isn't all that specific to save, but since these tests set up
+ * a copy of tests/root/ that is modifiable, it was convenient to put this
+ * test here.
+ */
+static void testPathEscaping(CuTest *tc) {
+    /* Path expression with characters escaped */
+    static const char *const weird =
+        "/files/etc/sysconfig/network-scripts/ifcfg-weird\\ \\[\\!\\]\\ \\(used\\ to\\ fail\\)";
+    /* Path without any escaping */
+    static const char *const weird_no_escape =
+        "/files/etc/sysconfig/network-scripts/ifcfg-weird [!] (used to fail)";
+
+    char *fname = NULL, *s = NULL;
+    const char *v;
+    int r;
+
+    /* Construct the file name in the file system and check the file is there */
+    r = asprintf(&fname, "%s%s", root, weird_no_escape + strlen("/files"));
+    CuAssertPositive(tc, r);
+
+    r = access(fname, R_OK);
+    CuAssertIntEquals(tc, 0, r);
+
+    /* Make sure weird is in the tree */
+    r = aug_match(aug, weird, NULL);
+    CuAssertIntEquals(tc, 1, r);
+
+    /* Make sure we can get to the metadata about weird */
+    r = asprintf(&s, "/augeas%s/path", weird);
+    CuAssertPositive(tc, r);
+
+    r = aug_get(aug, s, &v);
+    CuAssertIntEquals(tc, 1, r);
+    CuAssertStrEquals(tc, weird_no_escape, v);
+
+    /* Delete it from the tree and save it; make sure it gets removed
+       from the file system */
+    r = aug_rm(aug, weird);
+    CuAssertPositive(tc, r);
+
+    r = aug_save(aug);
+    CuAssertRetSuccess(tc, r);
+
+    r = access(fname, R_OK);
+    CuAssertIntEquals(tc, -1, r);
+    CuAssertIntEquals(tc, ENOENT, errno);
+}
+
 int main(void) {
     char *output = NULL;
     CuSuite* suite = CuSuiteNew();
@@ -293,6 +345,7 @@ int main(void) {
     SUITE_ADD_TEST(suite, testUmask077);
     SUITE_ADD_TEST(suite, testUmask027);
     SUITE_ADD_TEST(suite, testUmask022);
+    SUITE_ADD_TEST(suite, testPathEscaping);
 
     CuSuiteRun(suite);
     CuSuiteSummary(suite, &output);
diff --git a/tests/xpath.tests b/tests/xpath.tests
index 41bb555..e89cf98 100644
--- a/tests/xpath.tests
+++ b/tests/xpath.tests
@@ -278,7 +278,7 @@ test union (/files/etc/yum.conf | /files/etc/yum.repos.d/*)/*/gpgcheck
 
 # Paths with whitespace in them
 test php1 $php/mail function
-     /files/etc/php.ini/mail function
+     /files/etc/php.ini/mail\ function
 
 test php2 $php[mail function]
      /files/etc/php.ini
@@ -287,10 +287,10 @@ test php3 $php[count(mail function) = 1]
      /files/etc/php.ini
 
 test php4 $php/mail function/SMTP
-     /files/etc/php.ini/mail function/SMTP = localhost
+     /files/etc/php.ini/mail\ function/SMTP = localhost
 
 test php5 $php/mail\ function
-     /files/etc/php.ini/mail function
+     /files/etc/php.ini/mail\ function
 
 test expr-or /files/etc/group/root/*[self::gid or self::user]
      /files/etc/group/root/gid = 0
@@ -315,3 +315,19 @@ test ctx_file etc/network/interfaces
 
 test ctx_file_pred etc/network/interfaces/iface[. = "lo"]
      /files/etc/network/interfaces/iface[1] = lo
+
+# Test matching with characters that need escaping in the filename
+test escape1 /files/etc/sysconfig/network-scripts/*
+    /files/etc/sysconfig/network-scripts/ifcfg-eth0
+    /files/etc/sysconfig/network-scripts/ifcfg-wlan0
+    /files/etc/sysconfig/network-scripts/ifcfg-weird\ \[\!\]\ \(used\ to\ fail\)
+    /files/etc/sysconfig/network-scripts/ifcfg-Auto_FRITZ\!Box_Fon_WLAN_7112
+    /files/etc/sysconfig/network-scripts/ifcfg-lo
+    /files/etc/sysconfig/network-scripts/ifcfg-Auto-ALICE-WLAN38_\(automatisch\)
+    /files/etc/sysconfig/network-scripts/ifcfg-br0
+
+test escape2 /files/etc/sysconfig/network-scripts/ifcfg-weird\ \[\!\]\ \(used\ to\ fail\)/DEVICE
+     /files/etc/sysconfig/network-scripts/ifcfg-weird\ \[\!\]\ \(used\ to\ fail\)/DEVICE = weird
+
+test escape3 /files/etc/sysconfig/network-scripts/*[DEVICE = 'weird']
+     /files/etc/sysconfig/network-scripts/ifcfg-weird\ \[\!\]\ \(used\ to\ fail\)
