Javascript 确定某个路径是否是Node.js中另一个路径的子目录

Javascript 确定某个路径是否是Node.js中另一个路径的子目录,javascript,regex,node.js,Javascript,Regex,Node.js,我正在处理一个,我想为每个有事件侦听器的父目录发出一个事件。例如: 如果有以下可用的MQTT路径,其中有订阅程序-这些路径有事件侦听器- 测试 replyer/request test/replyer/request 如果有人在主题test/replyer/request/@issuer上发布,则应显示两个事件:test,test/replyer/request 如果没有任何路径是可能的,并且没有可用的有效事件列表,我们必须只检查一条路径是否是另一条路径的父路径。我们可以用正则表达式做这个吗

我正在处理一个,我想为每个有事件侦听器的父目录发出一个事件。例如:

如果有以下可用的MQTT路径,其中有订阅程序-这些路径有事件侦听器-

  • 测试
  • replyer/request
  • test/replyer/request
如果有人在主题
test/replyer/request/@issuer
上发布,则应显示两个事件:
test
test/replyer/request


如果没有任何路径是可能的,并且没有可用的有效事件列表,我们必须只检查一条路径是否是另一条路径的父路径。我们可以用正则表达式做这个吗?如果是这样,情况会是怎样的?有更简单/更有效的解决方案吗?

使用regex是一种解决方法(对于每个具有事件侦听器的路径,请检查发布的主题是否以该路径开头),但由于与使用超长URL相比,您更有可能拥有许多不同的路径,分解已发布的主题可能更有效

类似的内容可能也更容易阅读:

编辑:绝对正确,
indexOf==0
是我们真正需要的

let paths = [
    'test',
    'replyer/request',
    'test/replyer/request'
]

let topic = 'test/replyer/request/@issuer'

let respondingPaths = (paths, topic) => paths.filter(path => topic.indexOf(path) === 0)

console.log(respondingPaths(paths, topic)) // ['test', 'test/replyer/request']

使用
indexOf
检查子目录的路径是否以父目录的路径开始:

function isParentOf(parent, dir) {
    return dir.indexOf(parent) === 0;
}

isParentOf('test/replyer/request/@issuer', 'test') // true
isParentOf('test/replyer/request/@issuer', 'replyer/request') // false
isParentOf('test/replyer/request/@issuer', 'test/replyer/request') // true
2021答案 使用@Ilya的解决方案

2017年答案 在ES6中

如果您在node.js中工作,并且希望使其跨平台,请包括
path
模块,并将
split('/')
替换为
split(path.sep)


工作原理: 因此,您想知道一个目录(如
home/etc/subdirectory
)是否是另一个目录(如
home/etc
)的子目录

它采用假设的
子路径
父路径
并使用
拆分
将它们转换为数组:

['home', 'etc', 'subdirectory'], ['home', 'etc']
然后,它遍历
父数组中的所有标记,并使用ES6的
.every()
逐个检查它们在
子数组中的相对位置

如果父目录中的所有内容都与子目录中的所有内容匹配,并且知道我们已经排除了它们完全相同的目录(使用
child!==parent
),我们将得到答案


让节点本身来完成这项工作

const path = require('path');
const relative = path.relative(parent, dir);
return relative && !relative.startsWith('..') && !path.isAbsolute(relative);
它也为您实现了正常化

const path = require('path');

const tests = [
  ['/foo', '/foo'],
  ['/foo', '/bar'],
  ['/foo', '/foobar'],
  ['/foo', '/foo/bar'],
  ['/foo', '/foo/../bar'],
  ['/foo', '/foo/./bar'],
  ['/bar/../foo', '/foo/bar'],
  ['/foo', './bar'],
  ['C:\\Foo', 'C:\\Foo\\Bar'],
  ['C:\\Foo', 'C:\\Bar'],
  ['C:\\Foo', 'D:\\Foo\\Bar'],
];

tests.forEach(([parent, dir]) => {
    const relative = path.relative(parent, dir);
    const isSubdir = relative && !relative.startsWith('..') && !path.isAbsolute(relative);
    console.log(`[${parent}, ${dir}] => ${isSubdir} (${relative})`);
});
也可以跨驱动器在Windows上工作

[/foo, /foo] => false ()
[/foo, /bar] => false (..\bar)
[/foo, /foobar] => false (..\foobar)
[/foo, /foo/bar] => true (bar)
[/foo, /foo/../bar] => false (..\bar)
[/foo, /foo/./bar] => true (bar)
[/bar/../foo, /foo/bar] => true (bar)
[/foo, ./bar] => false (..\Users\kozhevnikov\Desktop\bar)
[C:\Foo, C:\Foo\Bar] => true (Bar)
[C:\Foo, C:\Bar] => false (..\Bar)
[C:\Foo, D:\Foo\Bar] => false (D:\Foo\Bar)

@dom vinyard的想法很好,但代码工作不正常,例如使用以下输入:

isChildOf('/x/y', '/x') //false
我在这里写了自己的版本:

function isParentOf(child, parent) {
  const childTokens = child.split('/').filter(i => i.length);
  const parentTokens = parent.split('/').filter(i => i.length);

  if (parentTokens.length > childTokens.length || childTokens.length === parentTokens.length) {
    return false;
  }

  return childTokens
    .slice(0, parentTokens.length)
    .every((childToken, index) => parentTokens[index] === childToken);
}

这里有几件事是防止失败所必需的:

  • 我们应该尝试解析文件系统路径吗?(我想是的)
  • 检查一个目录是否包含另一个目录应与符号链接一起使用
我提出了一个解决方案,它试图尽可能多地解析文件系统路径,同时允许存在或不存在的路径

  • 在操作系统的路径分隔符上拆分路径
  • 在文件系统中解析尽可能多的路径组件
  • 追加无法解析的剩余组件
  • 如果父级和子级之间的相对路径不是以
    开头path.sep
    且不是
    则父路径包含子路径
这一切都是可行的,假设任何不存在的路径组件将仅使用目录和文件创建(无符号链接)。例如,假设您的脚本只需要写入白名单中的路径,并且您正在接受不受信任的(用户提供的)文件名。您可以使用类似于with
$recursive=true
的方法创建子目录,一步创建目录结构,类似于

下面是代码(在堆栈溢出支持Node.js之前不可运行),重要的函数是
resolvefilesystemimpression()
pathContains()

const kWin32=false;
常数fs=要求('fs');
常量路径=kWin32?require('path')。win32:require('path');
//////////功能//////////
//解析文件系统中的路径(可能不存在),假设任何缺少的组件都是文件或目录(不是符号链接)
函数ResolveFileSystem(路径){
设余数=[];
为了(
让parts=path.normalize(thePath.split)(path.sep);//处理“/”或“\”路径分隔符的任意组合
零件长度>0;
remainders.unshift(parts.pop())
) {
试一试{
路径=
fs.realpathSync(parts.join(“/”)+//fs期望“/”实现跨平台兼容性
(remainders.length?path.sep+remainders.join(path.sep):“”);//如果所有尝试都失败,则路径保持不变
打破
}捕获(e){}
}
返回路径。规范化(路径);
}
//如果parentPath包含childPath,则返回true,假定任何缺少的组件都是文件或目录(不是符号链接)
函数pathContains(parentPath、childPath、resolvefilesystems=true){
如果(解析文件系统){
parentPath=ResolveFileSystemPassport(parentPath);
childPath=ResolveFileSystem(childPath);
}
const relativePath=path.relative(父路径、子路径);
return!relativePath.startsWith('..'+path.sep)&&relativePath!='..';
}
//////////文件/目录/符号链接创建//////////
log('目录内容:');
console.log();
试一试{
fs.mkdirSync(“母公司”);
}catch(e){}//如果已经存在,则抑制错误
fs.writeFileSync('parent/child.txt','Hello,world!');
试一试{
fs.mkdirSync(“外部”);
}catch(e){}//如果已经存在,则抑制错误
试一试{
fs.symlinkSync(path.relative('parent','outside'),'parent/child symlink');
}catch(e){}//如果已经存在,则抑制错误
fs.readdirSync('.').forEach(文件=>{
const stat=fs.lstatSync(文件);
console.log(
stat.isFile()
“文件”
:stat.isDirectory()?“dir”:
function isParentOf(child, parent) {
  const childTokens = child.split('/').filter(i => i.length);
  const parentTokens = parent.split('/').filter(i => i.length);

  if (parentTokens.length > childTokens.length || childTokens.length === parentTokens.length) {
    return false;
  }

  return childTokens
    .slice(0, parentTokens.length)
    .every((childToken, index) => parentTokens[index] === childToken);
}
const path = require('path');

function isAncestorDir(papa, child) {
    const papaDirs = papa.split(path.sep).filter(dir => dir!=='');
    const childDirs = child.split(path.sep).filter(dir => dir!=='');

    return papaDirs.every((dir, i) => childDirs[i] === dir);
}
assert(isAncestorDir('/path/to/parent', '/path/to/parent/and/child')===true);
assert(isAncestorDir('/path/to/parent', '/path/to')===false);
assert(isAncestorDir('/path/to/parent', '/path/to/parent')===true);
const PATH_SEPA = ['\\', '/'];

function isPathChildOf(path, parentPath, orEqual) {
    path = path.trim();
    parentPath = parentPath.trim();

    // trick: making sure the paths end with a separator
    let lastChar_path = path[path.length - 1];
    let lastChar_parentPath = path[parentPath.length - 1];
    if (lastChar_parentPath !== '\\' && lastChar_parentPath !== '/') parentPath += '/';
    if (lastChar_path !== '\\' && lastChar_path !== '/') path += '/';

    if (!orEqual && parentPath.length >= path.length) return false; // parent path should be smaller in characters then the child path (and they should be all the same from the start , if they differ in one char then they are not related)

    for (let i = 0; i < parentPath.length; i++) {
        // if both are not separators, then we compare (if one is separator, the other is not, the are different, then it return false, if they are both no separators, then it come down to comparaison, if they are same nothing happen, if they are different it return false)
        if (!(isPathSeparator(parentPath[i]) && isPathSeparator(path[i])) && parentPath[i] !== path[i]) {
            return false;
        }
    }
    return true;
}

function isPathSeparator(chr) {
    for (let i = 0; i < PATH_SEPA.length; i++) {
        if (chr === PATH_SEPA[i]) return true;
    }
    return false;
}
let path = '/ok/this/is/the/path';
let parentPath = '/ok/this/is';
let parentPath2 = '/ok/this/is/';
let parentPath3 = '/notok/this/is/different';

console.log("/ok/this/is/the/path' is child of /ok/this/is => " + isPathChildOf(path, parentPath));
console.log("/ok/this/is/the/path' is child of /ok/this/is/=> " + isPathChildOf(path, parentPath2));
console.log("/ok/this/is/' is child of /ok/this/is/ => " + isPathChildOf(parentPath2, parentPath2));
console.log("/ok/this/is/the/path' is child of /notok/this/is/different => " + isPathChildOf(path, parentPath3));

// test number 2:

console.log('test number 2 : ');
console.log("=============================");

let pthParent = '/look/at/this/path';
let pth = '/look/at/this/patholabi/hola'; // in normal use of indexof it will return true (know too we didn't use indexof just to support the different path separators, otherwise we would have used indexof in our function)

//expected result is false
console.log(`${pth}  is a child of ${pthParent}  ===>  ${isPathChildOf(pth, pthParent)}`);


let pthParent2 = '/look/at/this/path';
let pth2 = '/look/at/this/path/hola'; 

//expected result is true
console.log(`${pth2}  is a child of ${pthParent2}  ===>  ${isPathChildOf(pth2, pthParent2)}`);


let pthParent3 = '/look/at/this/path';
let pth3 = '/look/at/this/pathholabi'; 

//expected result is false
console.log(`${pth3}  is a child of ${pthParent3}  ===>  ${isPathChildOf(pth3, pthParent3)}`);

// test 3: equality
console.log('\ntest 3 : equality');
console.log("==========================");

let pParent =  "/this/is/same/Path";
let p =  "/this\\is/same/Path/";

console.log(`${p} is child of  ${pParent}   ====> ${isPathChildOf(p, pParent, true)}`);
import { relative } from 'path';

function isSubDirectory(parent, child) {
  return relative(child, parent).startsWith('..');
}
const path = require('path');

function isSubPathOf(subPath, parentPath) {

    parentPath = normalize(parentPath);

    if (subPath.length <= parentPath.length)
        return false;

    function normalize(p) {

        p = path.normalize(p);

        if (!p.endsWith(path.sep))
            p += path.sep;

        return p;
    }

    subPath = normalize(subPath);

    return subPath.startsWith(parentPath);
}

console.log(isSubPathOf('/a/b/c/d/e', '/a/b/c'));
console.log(isSubPathOf('/a/b/c/de', '/a/b/c'));
console.log(isSubPathOf('/a/b/c', '/a/y/c'));
console.log(isSubPathOf('/a/y/c/k', '/a/y/c'));