Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

[PATCH] hist, subst: add :R modifier for readlink(2)



i've wished for this for years. you can do it with zstat but it feels
like such an obvious complement to :[aAP]

i was sitting on it but i guess it's fine since it's new functionality

dana


diff --git a/Completion/Zsh/Type/_history_modifiers b/Completion/Zsh/Type/_history_modifiers
index 1a049d6cb..6810b032d 100644
--- a/Completion/Zsh/Type/_history_modifiers
+++ b/Completion/Zsh/Type/_history_modifiers
@@ -74,6 +74,7 @@ while true; do
 	"e:leave only extension"
 	"Q:strip quotes"
 	"P:realpath, resolve '..' physically"
+	"R:readlink, resolve non-recursively"
 	"l:lower case all words"
 	"u:upper case all words"
 	)
diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo
index 0cefaf7d1..1a8905ea2 100644
--- a/Doc/Zsh/expn.yo
+++ b/Doc/Zsh/expn.yo
@@ -308,6 +308,11 @@ zero) that are neither `tt(.)' nor `tt(/)' and that continue to the end
 of the string.  For example, the extension of
 `tt(foo.orig.c)' is `tt(.c)', and `tt(dir.c/foo)' has no extension.
 )
+item(tt(R))(
+Resolve a symlink non-recursively, like tt(readlink+LPAR()2+RPAR()).  If
+the input file name is not readable or not a symlink, it is returned
+unchanged.
+)
 xitem(tt(s/)var(l)tt(/)var(r)[tt(/)])
 item(tt(S/)var(l)tt(/)var(r)[tt(/)])(
 Substitute var(r) for var(l) as described below.
diff --git a/NEWS b/NEWS
index 4b26c0b03..031cd7817 100644
--- a/NEWS
+++ b/NEWS
@@ -72,6 +72,9 @@ The new completion helper _shadow can be used to temporarily wrap or
 substitute a function. The contrib function mkshadow makes it easier
 to use outside of completion contexts.
 
+The new modifier :R can be used in history expansion, parameter
+expansion, and glob qualifiers to non-recursively resolve a symlink.
+
 Changes since 5.8.1
 -------------------
 
diff --git a/Src/hist.c b/Src/hist.c
index ce5f7c20e..5f528ec78 100644
--- a/Src/hist.c
+++ b/Src/hist.c
@@ -944,6 +944,13 @@ histsubchar(int c)
 		}
 		sline = xsymlink(sline, 1);
 		break;
+	    case 'R':
+		if (!chreadlink(&sline)) {
+		    herrflush();
+		    zerr("modifier failed: R");
+		    return -1;
+		}
+		break;
 	    default:
 		herrflush();
 		zerr("illegal modifier: %c", c);
@@ -1873,6 +1880,35 @@ hcomsearch(char *str)
 
 /* various utilities for : modifiers */
 
+/*
+ * non-recursively resolve a symlink in junkptr. returns 0 on error, >0 on
+ * success. if the given path doesn't exist, isn't readable, or isn't a symlink,
+ * it is left unchanged and >0 is returned
+ */
+
+/**/
+int
+chreadlink(char **junkptr)
+{
+    ssize_t len;
+    char buf[PATH_MAX];
+
+    if (!**junkptr)
+	return 1;
+
+    untokenize(*junkptr);
+
+    if ((len = readlink(unmeta(*junkptr), buf, PATH_MAX)) > 0 &&
+	    len < PATH_MAX) {
+	buf[len] = '\0';
+	*junkptr = metafy(buf, len, META_HEAPDUP);
+	return 1;
+    }
+
+    return (len < PATH_MAX &&
+	(errno == ENOENT || errno == EACCES || errno == EINVAL));
+}
+
 /**/
 int
 chabspath(char **junkptr)
diff --git a/Src/subst.c b/Src/subst.c
index 56c1ad6dd..c9da0a8bd 100644
--- a/Src/subst.c
+++ b/Src/subst.c
@@ -4562,6 +4562,7 @@ modify(char **str, char **ptr, int inbrace)
 	    case 'q':
 	    case 'Q':
 	    case 'P':
+	    case 'R':
 		c = **ptr;
 		break;
 
@@ -4794,6 +4795,9 @@ modify(char **str, char **ptr, int inbrace)
 			}
 			copy = xsymlink(copy, 1);
 			break;
+		    case 'R':
+			chreadlink(&copy);
+			break;
 		    }
 		    tc = *tt;
 		    *tt = '\0';
@@ -4883,6 +4887,9 @@ modify(char **str, char **ptr, int inbrace)
 		    }
 		    *str = xsymlink(*str, 1);
 		    break;
+		case 'R':
+		    chreadlink(str);
+		    break;
 		}
 	    }
 	    if (rec < 0) {
diff --git a/Test/D02glob.ztst b/Test/D02glob.ztst
index 33458e3ba..66234291b 100644
--- a/Test/D02glob.ztst
+++ b/Test/D02glob.ztst
@@ -712,9 +712,22 @@
 >16001
 
  print -r ${${:-/a-b=c}:P}
-0:modifier ':P' with tokenised input
+0:modifier ':P' with tokenised input (workers/53671)
 >/a-b=c
 
+ : > myfile.tmp
+ ln -s mysrc.tmp mylink.tmp
+ () { print -r - ${1:R} } myfile.tmp # existing non-link file
+ () { print -r - ${1:R} } mylink.tmp # existing link with non-existent source
+ () { print -r - ${1:R} } nosuch     # non-existent link
+ print -r - ${${:-a-b=c}:R}          # tokenised input like above
+ rm myfile.tmp mylink.tmp
+0:modifier ':R'
+>myfile.tmp
+>mysrc.tmp
+>nosuch
+>a-b=c
+
  foo=a
  value="ac"
  print ${value//[${foo}b-z]/x}
diff --git a/Test/W01history.ztst b/Test/W01history.ztst
index 1d3f3cf6f..04fc7e31e 100644
--- a/Test/W01history.ztst
+++ b/Test/W01history.ztst
@@ -89,6 +89,21 @@ F:Check that a history bug introduced by workers/34160 is working again.
 >/my/path/for/testing
 >/my/path/for/testing
 
+ : > myfile.tmp
+ ln -s mysrc.tmp mylink.tmp
+ $ZTST_testdir/../Src/zsh -fis <<<'
+   : myfile.tmp
+   print -r - !$:R
+   : mylink.tmp
+   print -r - !$:R
+   : nosuch
+   print -r - !$:R' 2>/dev/null
+ rm myfile.tmp mylink.tmp
+0:Modifier :R
+>myfile.tmp
+>mysrc.tmp
+>nosuch
+
  $ZTST_testdir/../Src/zsh -fgis <<<'
  SAVEHIST=7
  print -rs "one\\"




Messages sorted by: Reverse Date, Date, Thread, Author