私は MQTTハンドラー に取り組んでいます。イベントリスナーがある親ディレクトリごとにイベントを発行したいと思います。例えば:
利用可能な以下のMQTTパスがあり、サブスクリプトがある場合–これらのパスにはイベントリスナーがあります–
test
replyer/request
test/replyer/request
そして誰かがトピックtest/replyer/request/@issuer
、2つのイベントがエミットされるはずです:test
、test/replyer/request
。
どのパスも可能であり、使用可能な有効なイベントのリストがない場合、パスが別のパスの親であるかどうかのみを確認する必要があります。これを正規表現で実行できますか?もしそうなら、それはどのように見えますか?よりシンプルで効率的なソリューションはありますか?
ES6の場合
_const isChildOf = (child, parent) => {
if (child === parent) return false
const parentTokens = parent.split('/').filter(i => i.length)
return parentTokens.every((t, i) => child.split('/')[i] === t)
}
_
node.jsで作業していて、クロスプラットフォームにしたい場合は、path
モジュールを含め、split('/')
をsplit(path.sep)
に置き換えます。
したがって、ディレクトリ(_home/etc/subdirectory
_など)が別のディレクトリ(_home/etc
_など)のサブディレクトリであるかどうかを確認する必要があります。
仮説のchild
パスとparent
パスの両方を取得し、split
を使用して配列に変換します。
_['home', 'etc', 'subdirectory'], ['home', 'etc']
_
次に、parent
配列内のすべてのトークンを反復処理し、ES6の.every()
を使用して、child
配列内の相対位置に対して1つずつ確認します。
親のすべてが子のすべてと一致し、それらがまったく同じディレクトリであることが除外されていることがわかっている場合(_child !== parent
_を使用)、答えが得られます。
Node自体が作業を行います。
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)
これは本当に古い質問ですが、ノードの組み込み path.relative を使用して、これに対する本当に簡単な解決策を思いつきました。子が親の内側にある場合、子からへの相対パスは常に..
で始まります。シンプル。
import { relative } from 'path';
function isSubDirectory(parent, child) {
return relative(child, parent).startsWith('..');
}
正規表現でそれを行うことはそれを回避する1つの方法です(イベントリスナーを持つすべてのパスについて、公開されたトピックがそのパスで始まっているかどうかを確認してください)。 URL、公開されたトピックを分解する方が効率的かもしれません。
このようなものもおそらく読みやすいでしょう:
Edit: @ huaoguo は間違いなく正しい、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']
失敗を防ぐために必要なことがいくつかあります。
私は、ファイルシステムのパスを可能な限り解決しようとする一方で、存在するかどうかに関係なくパスを許可するソリューションを考え出しました:
.. + path.sep
_で始まらず、_..
_ではない場合、親パスには子パスが含まれますこれはすべて機能し、存在しないパスコンポーネントはディレクトリとファイルのみを使用して作成されると想定します(シンボリックリンクなし)。たとえば、スクリプトがホワイトリストに登録されたパスにのみ書き込む必要があり、信頼できない(ユーザー指定の)ファイル名を受け入れるとします。 PHPのmkdir
と_$recursive = true
_を使用してサブディレクトリを作成し、 この例 と同様に、1つの手順でディレクトリ構造を作成できます。
以下はコードです(Stack OverflowがNode.jsをサポートするまで実行できません)。重要な関数はresolveFileSystemPath()
とpathContains()
です:
_const kWin32 = false;
const fs = require('fs');
const path = kWin32 ? require('path').win32 : require('path');
////////// functions //////////
// resolves (possibly nonexistent) path in filesystem, assuming that any missing components would be files or directories (not symlinks)
function resolveFileSystemPath(thePath) {
let remainders = [];
for (
let parts = path.normalize(thePath).split(path.sep); // handle any combination of "/" or "\" path separators
parts.length > 0;
remainders.unshift(parts.pop())
) {
try {
thePath =
fs.realpathSync(parts.join('/')) + // fs expects "/" for cross-platform compatibility
(remainders.length ? path.sep + remainders.join(path.sep) : ''); // if all attempts fail, then path remains unchanged
break;
} catch (e) {}
}
return path.normalize(thePath);
}
// returns true if parentPath contains childPath, assuming that any missing components would be files or directories (not symlinks)
function pathContains(parentPath, childPath, resolveFileSystemPaths = true) {
if (resolveFileSystemPaths) {
parentPath = resolveFileSystemPath(parentPath);
childPath = resolveFileSystemPath(childPath);
}
const relativePath = path.relative(parentPath, childPath);
return !relativePath.startsWith('..' + path.sep) && relativePath != '..';
}
////////// file/directory/symlink creation //////////
console.log('directory contents:');
console.log();
try {
fs.mkdirSync('parent');
} catch (e) {} // suppress error if already exists
fs.writeFileSync('parent/child.txt', 'Hello, world!');
try {
fs.mkdirSync('outside');
} catch (e) {} // suppress error if already exists
try {
fs.symlinkSync(path.relative('parent', 'outside'), 'parent/child-symlink');
} catch (e) {} // suppress error if already exists
fs.readdirSync('.').forEach(file => {
const stat = fs.lstatSync(file);
console.log(
stat.isFile()
? 'file'
: stat.isDirectory() ? 'dir ' : stat.isSymbolicLink() ? 'link' : ' ',
file
);
});
fs.readdirSync('parent').forEach(file => {
file = 'parent/' + file;
const stat = fs.lstatSync(file);
console.log(
stat.isFile()
? 'file'
: stat.isDirectory() ? 'dir ' : stat.isSymbolicLink() ? 'link' : ' ',
file
);
});
////////// tests //////////
console.log();
console.log(
"path.resolve('parent/child.txt'): ",
path.resolve('parent/child.txt')
);
console.log(
"fs.realpathSync('parent/child.txt'): ",
fs.realpathSync('parent/child.txt')
);
console.log(
"path.resolve('parent/child-symlink'): ",
path.resolve('parent/child-symlink')
);
console.log(
"fs.realpathSync('parent/child-symlink'):",
fs.realpathSync('parent/child-symlink')
);
console.log();
console.log(
'parent contains .: ',
pathContains('parent', '.', true)
);
console.log(
'parent contains ..: ',
pathContains('parent', '..', true)
);
console.log(
'parent contains parent: ',
pathContains('parent', 'parent', true)
);
console.log(
'parent contains parent/.: ',
pathContains('parent', 'parent/.', true)
);
console.log(
'parent contains parent/..: ',
pathContains('parent', 'parent/..', true)
);
console.log(
'parent contains parent/child.txt (unresolved): ',
pathContains('parent', 'parent/child.txt', false)
);
console.log(
'parent contains parent/child.txt (resolved): ',
pathContains('parent', 'parent/child.txt', true)
);
console.log(
'parent contains parent/child-symlink (unresolved):',
pathContains('parent', 'parent/child-symlink', false)
);
console.log(
'parent contains parent/child-symlink (resolved): ',
pathContains('parent', 'parent/child-symlink', true)
);
_
出力:
_directory contents:
file .bash_logout
file .bashrc
file .profile
file config.json
dir node_modules
dir outside
dir parent
link parent/child-symlink
file parent/child.txt
path.resolve('parent/child.txt'): /home/runner/parent/child.txt
fs.realpathSync('parent/child.txt'): /home/runner/parent/child.txt
path.resolve('parent/child-symlink'): /home/runner/parent/child-symlink
fs.realpathSync('parent/child-symlink'): /home/runner/outside
parent contains .: false
parent contains ..: false
parent contains parent: true
parent contains parent/.: true
parent contains parent/..: false
parent contains parent/child.txt (unresolved): true
parent contains parent/child.txt (resolved): true
parent contains parent/child-symlink (unresolved): true
parent contains parent/child-symlink (resolved): false
_
ライブの例: https://repl.it/repls/LawngreenWorriedGreyware
出力の最後の行は重要な行であり、解決されたファイルシステムパスが正しい結果につながる方法を示しています(その上の未解決の結果とは異なります)。
ファイルシステムの読み取り/書き込みを特定のディレクトリに制限することはセキュリティにとって非常に重要なので、Node.jsがこの機能を組み込みに組み込むことを望みます。これはネイティブのWindowsボックスでテストしていませんので、_kWin32
_フラグが機能しているかどうかをお知らせください。時間の許す限り、私はこの答えを整理するように努めます。
@ 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);
}
ここでは、indexOf
を使用する別のソリューション(または文字列を比較することで機能します)。
以下の関数では、複数のパス区切り文字をサポートするためにindexOf
を使用しませんでした。チェックすることはできますが、セパレーターが1つであることが確かな場合は、indexOf
を問題なく使用できます。
トリックは、パスがセパレータで終わるかどうかをチェックするか、ifそのようなセパレータをそれに追加するだけではありません。その場合、子パスに完全パスではない部分文字列があっても問題はありません。 [/this/isme_man
および/this/isme
](indexOf
(もちろんfalseの場合)を単純に使用する場合、1つ目は2つ目の子ですが、このようなトリックを使用する場合は[/this/isme/
および/this/isme_man/
]と同じindexOf
を使用して比較すると、問題はありません。
orEqual(子または等しい)でのチェックを許可するオプションがあることにも注意してください。これは3番目のオプションパラメーターです。
以下のコードを確認してください。
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)}`);
最後の例では、関数を使用して両方が子か等しいかを確認する方法を示しています(これは非常に少数です)。
また、splitメソッドの別の実装(正規表現エンジンを使用せずに複数のセパレーターを使用した分割メソッド)を含む、2つの関連するgithubリポジトリを確認できます。また、このメソッドもいくつか説明します(コメントを確認してください)コード内):
Dom Vinyardのコードに基づく&改善:
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);