【问题标题】:perl user input "dynamic/auto-tab expansion"perl 用户输入“动态/自动标签扩展”
【发布时间】:2013-12-20 02:54:08
【问题描述】:

我有一个小命令行工具的想法,希望它能让我更快地浏览目录,我需要知道如果给定一个来自 的字符,是否有可能在 perl 中自动将字符注入 取决于上下文(所以如果情况正确,它看起来像是一个不请自来的标签扩展)。

例如,假设我在某处的某个目录中有以下目录:

dir1 directory_2 test testing tested experiments

测试中包含的是目录

fully_tested partially_tested 

然后调用脚本将首先列出我调用的所有目录 cd-able 并等待输入。如果我输入的第一个字符是“t”,我想自动将“est”注入 STDIN(因为“test”在 test、testing 和tested 的联合中),列出所有以“test”开头的目录并等待进一步输入。

如果下一个字符是 'e' 或 'i' 它会自动 cd 分别进入测试或测试(因为这些目录是唯一确定的),如果它是一些尚未确定的“容易到达的关键”,比如* 我会 cd 进入测试,点击 '.'会做一个 cd ..,并且点击任何其他字母字符都没有效果(也许其他一些容易到达的键可能会列出所有内容)。

回到示例,输入字符 'tef' 意味着我将 cd 进入 testing/fully_tested(因为 'te' 唯一匹配 testing 而 'f' 唯一匹配 fully_tested)

至于实际编码,我希望它是我自己的问题(我相信可以逐个字符地处理输入)所以重申一下,我在这里要问的是:


perl 是否提供了某种机制,通过该机制可以在没有用户交互的情况下修改 STDIN(这样我可以在命令行上模拟 tab-expand,但这种扩展发生在幕后)?或者换句话说,在评估最后一个输入时,我想静默地进行选项卡扩展(根据命令行,它对我的​​输入流“看起来像一个选项卡扩展”)而不显式点击


更多细节:据我所知,我目前正在做什么,一旦编码完成,在运行时会如下所示(假设我的主目录有文件夹桌面、文档、Documents_bak、下载、音乐)

Desktop/ Documents/ Documents_bak/ Downloads/ Music
D
Desktop/ Documents/ Documents_bak/ Downloads
o
Documents/ Documents_bak/ Downloads
c
Documents/ Documents_bak
_
programming/ books/ ...(other directories)

这看起来不太好......我真正想要的是类似的东西

Desktop/ Documents/ Documents_bak/ Downloads/ Music
D
Desktop/ Documents/ Documents_bak/ Downloads
Do
Documents/ Documents_bak/ Downloads
Documents
Documents/ Documents_bak
Documents_bak/
programming/ books/ ...(other directories)
Documents_bak/

最后一行“Documents_bak/”实际上是输入流,就好像我已经全部输入了一样(所以如果我愿意,我可以删除其中的一些)。只是如何实现这一点 这给我带来了问题

编辑* 我不是(据我所知)试图在这里重新发明轮子——我只是想在更改目录时删除 、重新键入“cd”步骤。 IE。使用我的方案,只需“tef”即可 cd 进入测试/完全测试但在 bash 我需要“ticdf” 来做同样的事情.

【问题讨论】:

  • 所以你想在 perl 中重新创建 bash 等已经在做什么?
  • 不是真的...我还没有看到如何在 bash 中获得此功能。确实将选项卡视为一个键我怎么能遍历,比如 10 个目录只需 10 次击键(没有当然是一堆别名)?
  • 给定 abc/def/ghi, a[tab]/b[tab]/c[tab] 会得到你想要的,你的应用程序如何区分 abc/abcdef/ 的目录?应该选择哪一个?
  • 假设 'a' 唯一地扩展到一个目录,我想立即去那里,而不需要 步骤。另一方面,关于你的问题(我相信我在我的问题中承认了这一点),如果不是,它会在 abc 等我,如果我点击 d it cds 到 abcdef,如果我点击,说 * it cds进入 abc

标签: perl


【解决方案1】:

Term::ReadKey 可能是您问题的答案。它不能保证在所有系统上都能正常工作,但它可以提供您要求的控制。

这是一个在我的 Ubuntu 机器上运行良好的示例。

#!/usr/bin/perl
use strict;
use Term::ReadKey;

#a simple sub to return directories in supplied path 
sub list_dirs {
    my $path = shift;
    my @dirs;
    opendir DIR, $path or die "can't opendir '$path' : $!";
    my @dirs = grep {-d $path.'/'.$_} grep {$_ !~ m/^\.+$/} readdir DIR;
    closedir DIR;
    return @dirs;
}
#my start directory
my $home = '/path/to/startdir'; 
#my current directory, this will change
my $curdir = $home; 
#fetch the first list
my @dirs = list_dirs($curdir);

#read chars without pressing Enter
ReadMode 'cbreak'; 

#since we only need a char at a time
#we should remember our previous input
my $input;
#this var holds the real user input (without the autocomplete)
my $realinput;
#all the processing happens here, in the while loop, char by char
while (my $key = ReadKey(0)) {
    #validate input by allowed chars.
    #special chars like backspace, tab and enter can be handled separately
    if (ord($key) == 127) { #backspace
        chop $realinput;
        $input = $realinput;
        $key = '';
    #other special char checks can be added here
    } elsif ($key =~ m/^[\w\d\.\/\-]$/) { #allowed chars (more should be added)
        $realinput = $realinput.$key;
    } else {
        print "\ninvalid input\n$input";
        next; #ignore this char and ask for another
    }

    my @found = grep {$_ =~ m/^$input$key/} @dirs;
    #if we added a trailing slash or pressed enter, 
    #then limit our search to find one dir that exactly matches
    @found = grep {$_ =~ m/^$input$/} @dirs if ($key eq '/' || ord($key)==10);

    if  (scalar(@found) == 0) {
        #not dir matching, do nothing
        print "\nNo match\n$input";
    } elsif (scalar(@found) == 1) {
        #only one dir found
        #cd and reset input
        $curdir .= '/'.$found[0];
        my @dirs = list_dirs($curdir);
        $input = '';
        $realinput = '';
        print "\ncd to $curdir\n";
    } elsif (scalar(@found) > 1){
        print "\n".join(' ',map {$_.'/'} @found);
        $input .= $key;
        #find least match of matching dirs and autocomplete as much as possible 
        #assign the smallest matching string to $input and go to next loop
        for (my $i = length($input); $i <= length($found[0]); $i++) {
            my $matchcount = 0;
            my $tmpstr = substr($found[0], 0, $i);
            foreach my $d(@found) {
                $matchcount++ if ($d =~ m/^$tmpstr/);
            }
            if ($matchcount == scalar(@found)) {
                $input = $tmpstr;
            } else {
                last;
            }
        }
        print "\n$input";
    }
}

编辑:

我已经更新了我的脚本。添加了自动完成支持,例如在 bash 中按 TAB 时,以及退格支持和检查某些字符。此外,我还添加了一个用于列出目录的斜杠,并再次检查是否完全匹配。因此,如果您有目录 Documents/Documents_bak/,在输入中按 D 将自动完成到 Documents,您必须按 /Enter 到 cd 到 Documents/

我认为这个脚本是展示一切皆有可能的好方法。然而,每个目标都需要一定的工作量。替换用 perl 脚本编译的几乎 1MB 的 bash 不是一个现实的目标。但是您要求的行为在此脚本中得到了准确的演示。可以通过更多检查、更多特殊键处理和更多代码清理/优化来丰富它。

【讨论】:

  • 谢谢你,我会看看并研究这个链接。我同意实现应该很简单(因为我只是正则表达式字符串并重新显示一个新列表,或者以我是否找到唯一匹配为条件的 cd-ing),我真正遇到(或可能遇到)的唯一问题是在后台用东西填充 STDIN(所以就像 tab-completion 它看起来和行为就像你实际输入的东西,即使你没有输入)。一旦我完成了编码应该很简单:]
  • 进一步(请注意,我可以查看您的代码并在大多数情况下点头),我所追求的主要是以下行为:如果在命令行上 'd' 会展开到 'directory_' 我想在我的 perlscript 中获得“directory_”,就好像我实际上已经以与 tab-autocompletion 相同的方式输入了它;即看起来我实际上是输入了它,即使我没有输入。 (当然我不会在这里按下选项卡,因为它会响应 STDIN 得到一个 'd' 而我的脚本处理它)
  • @HexedAgain 如果我理解正确,您正在寻找I'm sleepy 部分。您不必填充 STDIN,只需将正确的值分配给 $input。我在脚本中进行的打印只是为了帮助用户了解正在发生的事情。 STDIN 就是你按下的 $key。一个字符。对于缺少的部分,我正在考虑一个循环,它采用substr($input,length($input),$i++) 并停止直到@found 的一个元素不匹配。我将在早上编辑我的答案以添加这部分
  • 您可以为我省去编写“我困了”的工作。 :] 我想要填充 STDIN 的原因是因为这是我期望 bash 在您点击选项卡时所做的事情(所以响应键盘上的事件 'k' + ,比如说;bash,我期望, 做一些低级操作系统的东西并在幕后填充 STDIN 的其余部分(所以如果我有一个目录 keyboard_shorcuts 和 keyboard_styles 例如,它可能会用 'eyboard_' 填充 STDIN)。它是同一种低级的东西我希望在 perl 中执行)
  • ...我实际上会做些什么来保存变量建立一个新的目录列表或可能的完成并在每个字符之后将它们打印出来,cd-ing 的行为就像你建议的那样很简单,这是我在这里渴望的制表符完成的“外观和感觉”。
猜你喜欢
  • 1970-01-01
  • 2016-12-20
  • 2021-12-02
  • 2021-11-28
  • 1970-01-01
  • 2016-09-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多