Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Yash 2.62 (Unreleased)

- In the `${parameter/pattern/replacement}` parameter expansion, an
empty pattern is now matched and replaced like any other match.
This allows `${var/#/prefix}` and `${var/%/suffix}` to prefix and
suffix the value, respectively.
- [line-editing] Updated the completion script for tar to support GNU
tar 1.35 and libarchive (bsdtar) 3.8.7.
- Updated the sample initialization script (yashrc):
Expand Down
4 changes: 4 additions & 0 deletions NEWS.ja
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Yash 2.62 (未リリース)

- パラメータ展開 `${parameter/pattern/replacement}` で、空のパターンも
他のマッチと同様にマッチ・置換されるようになった。これにより
`${var/#/prefix}` および `${var/%/suffix}` で値の先頭・末尾に
文字列を付け加えられる
- [行編集] tar の補完を GNU tar 1.35 および libarchive (bsdtar 3.8.7)
に合わせて更新
- 初期化スクリプト (yashrc) のサンプルを更新:
Expand Down
9 changes: 9 additions & 0 deletions doc/expand.txt
Original file line number Diff line number Diff line change
Expand Up @@ -336,23 +336,32 @@ If {{word1}} matches more than one part of the value, only the first part is
replaced.
The shortest matching is replaced if more than one matching is possible for
the same starting point in the value.
An empty {{word1}} matches the empty string at the beginning of the value, so
+${{{parameter}}/""/{{word2}}}+ prefixes {{word2}} to the value.
+
This modifier cannot be used in the link:posix.html[POSIXly-correct mode].

+/#{{word1}}/{{word2}}+::
This is similar to +/{{word1}}/{{word2}}+ above.
The only difference is that {{word1}} matches only at the beginning of the
value being expanded.
An empty {{word1}} matches the empty string at the beginning of the value, so
+${{{parameter}}/#/{{word2}}}+ prefixes {{word2}} to the value.

+/%{{word1}}/{{word2}}+::
This is similar to +/{{word1}}/{{word2}}+ above.
The only difference is that {{word1}} matches only at the end of the value
being expanded.
An empty {{word1}} matches the empty string at the end of the value, so
+${{{parameter}}/%/{{word2}}}+ suffixes {{word2}} to the value.

+//{{word1}}/{{word2}}+::
This is similar to +/{{word1}}/{{word2}}+ above.
The only difference is that all matched parts are replaced if {{word1}}
matches more than one part of the value.
An empty {{word1}} matches the empty string at the beginning of the value,
between each two adjacent characters, and at the end of the value, so all
those empty matches are replaced with {{word2}}.

+:/{{word1}}/{{word2}}+::
This is similar to +/{{word1}}/{{word2}}+ above.
Expand Down
8 changes: 4 additions & 4 deletions doc/ja/expand.txt
Original file line number Diff line number Diff line change
Expand Up @@ -195,18 +195,18 @@ echo "${array[2,-2]}"
この加工指定は +%{{単語}}+ と同様ですが、マッチの仕方が複数通りある場合はできるだけ長くマッチさせる点が異なります。

+/{{単語1}}/{{単語2}}+::
{{単語1}}をパターンとして見たとき、それがいま展開しようとしている値の一部にマッチするならば、そのマッチする部分を{{単語2}}に置き換えます。結果として、このパラメータ展開はマッチした部分を{{単語2}}に置き換えた値に展開されます。マッチする箇所が複数ある場合は、最初の箇所が選ばれます。マッチの仕方が複数通りある場合はできるだけ長くマッチさせます。
{{単語1}}をパターンとして見たとき、それがいま展開しようとしている値の一部にマッチするならば、そのマッチする部分を{{単語2}}に置き換えます。結果として、このパラメータ展開はマッチした部分を{{単語2}}に置き換えた値に展開されます。マッチする箇所が複数ある場合は、最初の箇所が選ばれます。マッチの仕方が複数通りある場合はできるだけ長くマッチさせます。{{単語1}}が空文字列の場合は値の先頭の空文字列にマッチするので、+${{{パラメータ名}}/""/{{単語2}}}+ は値の先頭に{{単語2}}を付け加えた値に展開されます。
+
この加工指定は link:posix.html[POSIX 準拠モード]では使えません。

+/#{{単語1}}/{{単語2}}+::
この加工指定は +/{{単語1}}/{{単語2}}+ と同様ですが、いま展開しようとしている値の先頭部分にしかマッチしない点が異なります。
この加工指定は +/{{単語1}}/{{単語2}}+ と同様ですが、いま展開しようとしている値の先頭部分にしかマッチしない点が異なります。{{単語1}}が空文字列の場合は値の先頭の空文字列にマッチするので、+${{{パラメータ名}}/#/{{単語2}}}+ は値の先頭に{{単語2}}を付け加えた値に展開されます。

+/%{{単語1}}/{{単語2}}+::
この加工指定は +/{{単語1}}/{{単語2}}+ と同様ですが、いま展開しようとしている値の末尾部分にしかマッチしない点が異なります。
この加工指定は +/{{単語1}}/{{単語2}}+ と同様ですが、いま展開しようとしている値の末尾部分にしかマッチしない点が異なります。{{単語1}}が空文字列の場合は値の末尾の空文字列にマッチするので、+${{{パラメータ名}}/%/{{単語2}}}+ は値の末尾に{{単語2}}を付け加えた値に展開されます。

+//{{単語1}}/{{単語2}}+::
この加工指定は +/{{単語1}}/{{単語2}}+ と同様ですが、マッチする箇所が複数ある場合は最初の箇所だけではなく全ての箇所を{{単語2}}に置き換える点が異なります。
この加工指定は +/{{単語1}}/{{単語2}}+ と同様ですが、マッチする箇所が複数ある場合は最初の箇所だけではなく全ての箇所を{{単語2}}に置き換える点が異なります。{{単語1}}が空文字列の場合は値の先頭・末尾および隣接する各文字の間の空文字列にマッチするので、それら全てが{{単語2}}に置き換えられます。

+:/{{単語1}}/{{単語2}}+::
この加工指定は +/{{単語1}}/{{単語2}}+ と同様ですが、いま展開しようとしている値全体にマッチする場合しか対象としない点が異なります。
Expand Down
24 changes: 22 additions & 2 deletions tests/param-y.tst
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ a='123/456/789' b='1*2?3' HOME=/
bracket ${a/4*6/x} ${a/\//y} ${a/\//} ${a/\/}
bracket ${a/#*3/x} ${a/#456/y}
bracket ${a/%7*/x} ${a/%456/y}
bracket ${a//4*6/x} ${a//\//y} ${a//\//} ${a//\/}
bracket ${a//4*6/x} ${a//\//y} ${a//\//} ${a//\/} ${a//[[:digit:]]/x}
bracket ${a:/1*9/x} ${a:/2*9/x} ${a:/1*8/x}
bracket ${b/\**\?/x} ${b/"*"?'?'/x}
bracket ${a/5/~/\*'*'"*"} ${a//~}
Expand All @@ -465,14 +465,34 @@ __IN__
[123/x/789][123y456/789][123456/789][123456/789]
[x/456/789][123/456/789]
[123/456/x][123/456/789]
[123/x/789][123y456y789][123456789][123456789]
[123/x/789][123y456y789][123456789][123456789][xxx/xxx/xxx]
[x][123/456/789][123/456/789]
[1x3][1x3]
[123/4/***6/789][123456789]
[1_2_3][1_2_3]
__OUT__
# XXX: Should the last one (${a/*/"$b"}) expand to 1*2?3 rather than 1_2_3?

test_oE '${a/b/c} with empty match'
a='123/456/789' b=''
bracket ${a/#/x} ${a/%/x}
bracket ${a/""/x} ${a//""/x}
bracket ${a//*/x} ${b//*/x}
bracket ${a/#1*/x} ${a/%9*/x}
bracket ${a/#2/x} ${a/%8/x}
set -- 1 22 333
bracket "${@/#/p}" "${@/%/s}"
bracket ${b/#/x} ${b/%/x} ${b//""/x}
__IN__
[x123/456/789][123/456/789x]
[x123/456/789][x1x2x3x/x4x5x6x/x7x8x9x]
[x][x]
[x][123/456/78x]
[123/456/789][123/456/789]
[p1][p22][p333][1s][22s][333s]
[x][x][x]
__OUT__

test_oE 'scalar parameter index'
a='1-2-3'
bracket @ "${a[@]}"
Expand Down
15 changes: 12 additions & 3 deletions xfnmatch.c
Original file line number Diff line number Diff line change
Expand Up @@ -687,8 +687,10 @@ xfnmresult_T wmatch_longest(

/* Substitutes part of string `s' that matches pre-compiled pattern `xfnm'
* with string `repl'. If `substall' is true, all matching substrings in `s' are
* substituted. Otherwise, only the first match is substituted. The resulting
* string is returned as a newly-malloced string. */
* substituted. Otherwise, only the first match is substituted. An empty match
* is substituted like any other match; when substituting all matches, one
* character is skipped after each empty match to ensure progress. The
* resulting string is returned as a newly-malloced string. */
wchar_t *xfnm_subst(const xfnmatch_T *restrict xfnm, const wchar_t *restrict s,
const wchar_t *restrict repl, bool substall)
{
Expand All @@ -711,11 +713,18 @@ wchar_t *xfnm_subst(const xfnmatch_T *restrict xfnm, const wchar_t *restrict s,
wb_init(&buf);
do {
xfnmresult_T result = xfnm_wmatch(xfnm, &s[i]);
if (result.start == (size_t) -1 || result.start >= result.end)
if (result.start == (size_t) -1)
break;
wb_ncat(&buf, &s[i], result.start);
wb_cat(&buf, repl);
i += result.end;
if (s[i] == L'\0')
break;
if (result.start == result.end) {
/* empty match: skip one character to ensure progress */
wb_wccat(&buf, s[i]);
i++;
}
} while (substall);
return wb_towcs(wb_cat(&buf, &s[i]));
}
Expand Down
Loading