mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Adding support for native command globbing on UNIX (#3643)
This change enables globbing (wildcard expansion) against the file system for native commands like '/bin/ls'. The expansion is only done in the file system. In non-filesystem drives expansion is not done and the pattern is returned unchanged. Limitations of the fix: Currently quoting is not honored so for a command like /bin/ls "*.txt", wildcard expansion will still be done. Adding support for bare word detection will come in a future PR. Use --% to suppress wildcard expansion e.g. git add --% *
This commit is contained in:
committed by
Dongbo Wang
parent
c0aafdb3d0
commit
11ad02a15a
@@ -192,25 +192,8 @@ namespace System.Management.Automation
|
||||
//
|
||||
// We need to check quotes that the win32 argument parser checks which is currently
|
||||
// just the normal double quotes, no other special quotes. Also note that mismatched
|
||||
// quotes are supported.
|
||||
|
||||
bool needQuotes = false, followingBackslash = false;
|
||||
int quoteCount = 0;
|
||||
for (int i = 0; i < arg.Length; i++)
|
||||
{
|
||||
if (arg[i] == '"' && !followingBackslash)
|
||||
{
|
||||
quoteCount += 1;
|
||||
}
|
||||
else if (char.IsWhiteSpace(arg[i]) && (quoteCount % 2 == 0))
|
||||
{
|
||||
needQuotes = true;
|
||||
}
|
||||
|
||||
followingBackslash = arg[i] == '\\';
|
||||
}
|
||||
|
||||
if (needQuotes)
|
||||
// quotes are supported
|
||||
if (NeedQuotes(arg))
|
||||
{
|
||||
_arguments.Append('"');
|
||||
_arguments.Append(arg);
|
||||
@@ -218,13 +201,131 @@ namespace System.Management.Automation
|
||||
}
|
||||
else
|
||||
{
|
||||
#if UNIX
|
||||
// On UNIX systems, we expand arguments containing wildcard expressions against
|
||||
// the file system just like bash, etc.
|
||||
if (System.Management.Automation.WildcardPattern.ContainsWildcardCharacters(arg))
|
||||
{
|
||||
// See if the current working directory is a filesystem provider location
|
||||
// We won't do the expansion if it isn't since native commands can only access the file system.
|
||||
var cwdinfo = Context.EngineSessionState.CurrentLocation;
|
||||
|
||||
// If it's a filesystem location then expand the wildcards
|
||||
if (string.Equals(cwdinfo.Provider.Name, Microsoft.PowerShell.Commands.FileSystemProvider.ProviderName,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
bool normalizePath = true;
|
||||
// On UNIX, paths starting with ~ are not normalized
|
||||
if (arg.Length > 0 && arg[0] == '~')
|
||||
{
|
||||
normalizePath = false;
|
||||
}
|
||||
|
||||
// See if there are any matching paths otherwise just add the pattern as the argument
|
||||
var paths = Context.EngineSessionState.InvokeProvider.ChildItem.Get(arg, false);
|
||||
if (paths.Count > 0)
|
||||
{
|
||||
bool first = true;
|
||||
foreach (var path in paths)
|
||||
{
|
||||
object pbo = path.BaseObject;
|
||||
if (! first)
|
||||
{
|
||||
_arguments.Append(" ");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (! (pbo is System.IO.FileSystemInfo))
|
||||
{
|
||||
// If the object is not a filesystem object, then just append
|
||||
// the pattern unchanged
|
||||
_arguments.Append(arg);
|
||||
break;
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
var expandedPath = (pbo as System.IO.FileSystemInfo).FullName;
|
||||
if (normalizePath)
|
||||
{
|
||||
expandedPath = Context.SessionState.Path.NormalizeRelativePath(expandedPath, cwdinfo.ProviderPath);
|
||||
}
|
||||
// If the path contains spaces, then add quotes around it.
|
||||
if (NeedQuotes(expandedPath))
|
||||
{
|
||||
_arguments.Append("\"");
|
||||
_arguments.Append(expandedPath);
|
||||
_arguments.Append("\"");
|
||||
}
|
||||
else
|
||||
{
|
||||
_arguments.Append(expandedPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_arguments.Append(arg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_arguments.Append(arg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Even if there are no wildcards, we still need to possibly
|
||||
// expand ~ into the filesystem provider home directory path
|
||||
ProviderInfo fileSystemProvider = Context.EngineSessionState.GetSingleProvider(
|
||||
Microsoft.PowerShell.Commands.FileSystemProvider.ProviderName);
|
||||
string home = fileSystemProvider.Home;
|
||||
if (string.Equals(arg, "~"))
|
||||
{
|
||||
_arguments.Append(home);
|
||||
}
|
||||
else if (arg.StartsWith("~/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var replacementString = home + arg.Substring(1);
|
||||
_arguments.Append(replacementString);
|
||||
}
|
||||
else
|
||||
{
|
||||
_arguments.Append(arg);
|
||||
}
|
||||
}
|
||||
#else
|
||||
_arguments.Append(arg);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (list != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check to see if the string contains spaces and therefore must be quoted.
|
||||
/// </summary>
|
||||
/// <param name="stringToCheck">The string to check for spaces</param>
|
||||
private bool NeedQuotes(string stringToCheck)
|
||||
{
|
||||
bool needQuotes = false, followingBackslash = false;
|
||||
int quoteCount = 0;
|
||||
for (int i = 0; i < stringToCheck.Length; i++)
|
||||
{
|
||||
if (stringToCheck[i] == '"' && !followingBackslash)
|
||||
{
|
||||
quoteCount += 1;
|
||||
}
|
||||
else if (char.IsWhiteSpace(stringToCheck[i]) && (quoteCount % 2 == 0))
|
||||
{
|
||||
needQuotes = true;
|
||||
}
|
||||
followingBackslash = stringToCheck[i] == '\\';
|
||||
}
|
||||
return needQuotes;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The native command to bind to
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
|
||||
Describe 'Native UNIX globbing tests' -tags "CI" {
|
||||
|
||||
BeforeAll {
|
||||
if (-not $IsWindows )
|
||||
{
|
||||
"" > "$TESTDRIVE/abc.txt"
|
||||
"" > "$TESTDRIVE/bbb.txt"
|
||||
"" > "$TESTDRIVE/cbb.txt"
|
||||
}
|
||||
|
||||
$defaultParamValues = $PSDefaultParameterValues.Clone()
|
||||
$PSDefaultParameterValues["it:skip"] = $IsWindows
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
$global:PSDefaultParameterValues = $defaultParamValues
|
||||
}
|
||||
|
||||
# Test * expansion
|
||||
It 'The globbing pattern *.txt should match 3 files' {
|
||||
(/bin/ls $TESTDRIVE/*.txt).Length | Should Be 3
|
||||
}
|
||||
It 'The globbing pattern *b.txt should match 2 files whose basenames end in "b"' {
|
||||
(/bin/ls $TESTDRIVE/*b.txt).Length | Should Be 2
|
||||
}
|
||||
# Test character classes
|
||||
It 'The globbing pattern should match 2 files whose names start with either "a" or "b"' {
|
||||
(/bin/ls $TESTDRIVE/[ab]*.txt).Length | Should Be 2
|
||||
}
|
||||
It 'Globbing abc.* should return one file name "abc.txt"' {
|
||||
/bin/ls $TESTDRIVE/abc.* | Should Match "abc.txt"
|
||||
}
|
||||
# Test that ? matches any single character
|
||||
It 'Globbing [cde]b?.* should return one file name "cbb.txt"' {
|
||||
/bin/ls $TESTDRIVE/[cde]b?.* | Should Match "cbb.txt"
|
||||
}
|
||||
It 'Should return the original pattern if there are no matches' {
|
||||
/bin/echo $TESTDRIVE/*.nosuchfile | Should Match "\*\.nosuchfile$"
|
||||
}
|
||||
# Test the behavior in non-filesystem drives
|
||||
It 'Should not expand patterns on non-filesystem drives' {
|
||||
/bin/echo env:ps* | Should BeExactly "env:ps*"
|
||||
}
|
||||
# Test the behavior for files with spaces in the names
|
||||
It 'Globbing filenames with spaces should match 2 files' {
|
||||
"" > "$TESTDRIVE/foo bar.txt"
|
||||
"" > "$TESTDRIVE/foo baz.txt"
|
||||
(/bin/ls $TESTDRIVE/foo*.txt).Length | Should Be 2
|
||||
}
|
||||
# Test ~ expansion
|
||||
It 'Tilde should be replaced by the filesystem provider home directory' {
|
||||
/bin/echo ~ | Should BeExactly ($executioncontext.SessionState.Provider.Get("FileSystem").Home)
|
||||
}
|
||||
# Test ~ expansion with a path fragment (e.g. ~/foo)
|
||||
It '~/foo should be replaced by the <filesystem provider home directory>/foo' {
|
||||
/bin/echo ~/foo | Should BeExactly "$($executioncontext.SessionState.Provider.Get("FileSystem").Home)/foo"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user