perl用户输入“动态/自动标签扩展”

时间:2013-12-03 20:56:57

标签: perl

我有一个小命令行工具的想法,希望能让我更快地导航目录,我需要知道是否给出了来自< STDIN>从perl中,可以自动地将字符注入到< STDIN>取决于上下文(如果情况正确,它看起来像是一个未经请求的制表符扩展)。

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

dir1 directory_2 test testing tested experiments

和测试中包含的目录是

fully_tested partially_tested 

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

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

回到示例,键入chars'tef'意味着我将进入testing / fully_tested(因为'te'唯一匹配测试,'f'唯一匹配fully_tested)

至于实际对此进行编码我希望它是我自己的问题(我相信可以在char的基础上处理char的输入)所以重申一下,我在这里问的是:


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


更多细节:据我所知,我目前正在做什么,一旦编码,我最终会在运行时看起来如下(假设我的主目录有文件夹)桌面,文档,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 /”实际上是输入流,好像我已经输入了所有内容(所以如果我愿意,我可以删除其中一些)。它只是如何实现这一点 这给我带来了一个问题

编辑*我不是(据我所知)试图在这里重新发明轮子 - 我只是试图削减< TAB>,< RET>,在更改目录时重新键入'cd'步骤。即使用我的方案,只需“tef”即可进入测试/完全测试但仅仅在bash中我需要“t< TAB> i< TAB>< RET> cd< SPACE> f< TAB>< RET>“做同样的事。

1 个答案:

答案 0 :(得分:2)

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才能转到{ {1}}。

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