Perl oneliner для сопоставления точного слова в пути со многими различными значениями со специальными символами

Как точно сопоставить значение $TARGET_NAME из вывода find /tmp -type l -exec ls -l?

 $ find /tmp -type l -exec ls -l 2>/dev/null {} +
 lrwxrwxrwx 1 root root  24 Mar 18 12:41 /tmp/test/link -> /usr/admin/Collect_tests
 lrwxrwxrwx 1 root root  43 Mar 18 12:41 /tmp/test/link1 -> /usr/admin/Collect_tests/[email protected]
 lrwxrwxrwx 1 root root  68 Mar 18 12:41 /tmp/test/link2 -> /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com
 lrwxrwxrwx 1 root root 100 Mar 18 12:42 /tmp/test/link3 -> /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com/List.files.emails.dummy*Printed
 lrwxrwxrwx 1 root root  92 Mar 18 12:42 /tmp/test/link4 -> /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com/[email protected]

Примеры значений

 [email protected]
 TARGET_NAME=Upload_Shema@@@.DATA.com
 TARGET_NAME=List.files.emails.dummy*Printed

Цель: вывести: "имя ссылки" и "ПУТЬ" (последнее поле) только в том случае, если $TARGET_NAME точно соответствует слову в последнем поле.

Пример (когда мы хотим точного совпадения - тогда TARGET_NAME=Upload_Shema@@@.DATA.com):

Результаты будут отображаться следующим образом

/tmp/test/link2 /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com
/tmp/test/link3 /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com/List.files.emails.dummy*Printed
/tmp/test/link4 /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com/[email protected]

Есть несколько условий:

1) Нужно сопоставить только последнее поле (из вывода ls -l)

Пример

      /usr/admin/Collect_tests/[email protected]

2) Значение $TARGET_NAME должно соответствовать всему слову

Пример полного соответствия (в то время как [email protected]):

    /usr/admin/Collect_tests/[email protected]

Пример неполного совпадения:

    /usr/admin/Collect_tests/[email protected]

3) Слева от $TARGET_NAME должна быть обратная косая черта ("/"), а справа от $TARGET_NAME должна быть обратная косая черта или конец строки.

4) Необходимо экранировать специальные символы: " / " , " @ " . " * " и т. д.

5) Код будет частью сценария ksh (и может быть реализован с помощью однострочника Perl или AWK). или кш и т.п.. )

Пример

   find /tmp -type l -exec ls -l 2>/dev/null {} + | < Perl one liner .............. >    

person Community    schedule 18.03.2013    source источник
comment
meta.stackexchange.com/questions/66377/what -is-the-xy-проблема ?   -  person TLP    schedule 18.03.2013
comment
Не анализировать вывод ls, даже если он исходит от find   -  person Gilles Quenot    schedule 18.03.2013
comment
@Sputnick У меня нет другой альтернативы (мне нужно проверить все PATH, на которые указывают ссылки)   -  person    schedule 18.03.2013
comment
Да, у вас есть пример: find -type l -printf '%l\n' это лучшее начало.   -  person Gilles Quenot    schedule 18.03.2013
comment
@Sputnick, спасибо, это новая информация для меня.   -  person    schedule 18.03.2013
comment
@Sputnick, как напечатать также имя ссылки перед последним полем?   -  person    schedule 18.03.2013


Ответы (4)


Как упоминалось в ответ на ваш последний вопрос (поскольку он удален), синтаксический анализ вывода ls очень неоптимален. Вместо этого можно использовать readlink.

find /tmp -type l -exec \
   perl -e'
      my $TARGET_NAME = shift;
      for (@ARGV) {
         my $p = readlink($_);
         $p =~ m{(?:^|/)\Q$TARGET_NAME\E(?:/|\z)}
            or next;
         print("$_\t$p\n");
      }
   ' "$TARGET_NAME" {} \;

Или более эффективно,

perl -MFile::Find::Rule -e'
   my ($TARGET_NAME, $BASE) = @ARGV;
   for (File::Find::Rule->symlink->in($BASE)) {
      my $p = readlink($_);
      $p =~ m{(?:^|/)\Q$TARGET_NAME\E(?:/|\z)}
         or next;
      print("$_\t$p\n");
   }
' "$TARGET_NAME" /tmp

По запросу это будет соответствовать

TARGET_NAME
TARGET_NAME/
TARGET_NAME/x
.../TARGET_NAME
.../TARGET_NAME/
.../TARGET_NAME/x

но нет

TARGET_NAMEx/...
.../TARGET_NAMEx
.../TARGET_NAMEx/...
xTARGET_NAME/...
.../xTARGET_NAME
.../xTARGET_NAME/...

Примечание. Измените find ... -exec ... \; на find ... -exec ... +, если ваш find поддерживает это.

person ikegami    schedule 18.03.2013
comment
(это замечание - то же самое замечание, которое я сказал Олафу), если я установил TARGET_NAME=Collect_tests, то я ожидал увидеть 6 совпадающих строк, см. подробности моего вопроса, поэтому ... (для всех ссылок ссылка, ссылка1, ссылка2, ссылка3, ссылка4, ссылка5 ) код печатает только ссылку Collect_tests замечание - причина, по которой я хочу напечатать все строки, соответствующие $TARGET_NAME , потому что мне нужно позже заново создать новые ссылки - person ; 19.03.2013
comment
Вторая проблема: код не работает на Solaris 10... не могу понять? (нет вывода из кода) - person ; 19.03.2013
comment
уг, ваш пост говорит прямо противоположное вашему первому комментарию. Я исправил ваш вопрос и соответствующим образом скорректировал свой ответ. - person ikegami; 19.03.2013
comment
И я не смогу решить твою проблему с Солярисом, если ты не скажешь мне, в чем проблема. (find ничего не выдает? @ARGV пусто? Регулярное выражение не соответствует?) Приложите уже усилия! - person ikegami; 19.03.2013
comment
Знаете ли вы, почему ваш первый код не подходит для Solaris 10? (работает только на linux) - person ; 19.03.2013
comment
Нет, именно поэтому я попросил вас выяснить это. Я действительно не должен был говорить вам! - person ikegami; 19.03.2013
comment
Хорошо, я попытаюсь выяснить (но синтаксис Perl должен быть одинаковым в одной и той же ОС, так что очень сложно понять, почему?) - person ; 19.03.2013
comment
Я смущен. Это синтаксическая ошибка или вы не получаете вывод? - person ikegami; 19.03.2013
comment
Добавление -w или добавление or die $! к readlink может пролить свет - person ikegami; 19.03.2013
comment
давайте продолжим обсуждение в чате - person ; 19.03.2013
comment
о версии Perl в Solaris — это версия perl v5.8.7, созданная для sun4-solaris-thread-multi, и версия perl в Linux — это версия Perl v5.8.8, созданная для i686-linux - person ; 19.03.2013
comment
Можем ли мы сделать что-то еще, чтобы найти проблему - почему на Solaris первый код ничего не печатает? - person ; 19.03.2013
comment
@ Эйтан, что еще? Вы не сказали, что вы пытались. Я сказал вам 4 вещи, чтобы проверить. Так какой из них не дает то, что вы хотите? - person ikegami; 19.03.2013
comment
Я нахожу проблему - / перед \Q , поэтому, если я уберу обратную косую черту, она будет работать на обеих ОС. - person ; 19.03.2013
comment
мне непонятно, зачем вы добавляете / перед \Q? , можете ли вы обновить свой ответ и удалить / , - person ; 19.03.2013
comment
Нет, я не могу удалить /, потому что тогда он будет соответствовать .../xTARGET_NAME/.... Если это не соответствует, это потому, что вы ошиблись в своем вопросе (снова). Я могу ответить только на тот вопрос, который вы на самом деле задали. Если ваш вопрос неверен, начните с его исправления. - person ikegami; 20.03.2013
comment
но причина в том, что он не работает на Solaris, как вы знаете, на Linux он работает, но не на Solaris, поэтому я удаляю /, а затем он работает на Solaris... так что вы думаете... можете ли вы предоставить другую альтернативу? вместо / - хорошо, теперь вы знаете проблему - person ; 20.03.2013
comment
@ Эйтан, альтернатива? Он отлично подходит для заданного вами вопроса. - person ikegami; 20.03.2013
comment
Не так. Он отлично работает на Solaris для ссылок, которые, как вы сказали, должны работать. Опять же, я могу ответить только на вопрос, который вы на самом деле задаете. Я устал гадать, потому что ты не хочешь делать никакой работы. - person ikegami; 20.03.2013
comment
на моем Solaris это не работает, у меня Solaris 10, вы проверяли его на ОС Solaris? , какая у вас версия Perl на Solaris ? - person ; 20.03.2013
comment
У меня нет доступа к Солярису. Мне не нужен доступ к Solaris, чтобы знать, что ЭТО ОТЛИЧНО РАБОТАЕТ НА SOLARIS ДЛЯ ССЫЛОК, НА КОТОРЫХ ВЫ СКАЗАЛЫ, ЧТО ЭТО ДОЛЖНО РАБОТАТЬ. Если это не работает на Солярисе, то ВАШ ВОПРОС НЕПРАВИЛЬНЫЙ. - person ikegami; 20.03.2013
comment
ОК, я нахожу проблему, вы правы - иногда имя каталога/файла не начинается с /, например ссылка1 -> Collect_tests, поэтому в этом случае не будет совпадения, что правильно - код должен поддерживать, если / существует или, если не раньше, чем dir/file, можете ли вы обновить свое решение в соответствии с тем, что я сказал, и я добавлю об этом замечание в свой вопрос? - person ; 20.03.2013
comment
Хорошо, а как вы думаете, что нужно для соответствия началу строки или /, а не только /? - person ikegami; 20.03.2013
comment
Но это просто - пробел, правило будет пробелом|/ - person ; 20.03.2013
comment
да, нет, это не будет соответствовать началу строки. ^ будет, так что (?:^|/)! Скорректированный код соответственно. - person ikegami; 20.03.2013

Поскольку цель может совпадать или не совпадать с возвращаемой частью, проще всего сделать два отдельных вызова регулярных выражений (также может помочь, если это следует обобщить):

perl -ne 'print "$1" if (m#[email protected]# && m#([^/]+)\s*$#);'

Другими словами, найдите целевую фразу, а затем получите последний компонент поиска (тот, который не содержит «/»). Когда оба условия выполнены, напечатайте захваченный текст в круглых скобках.

Относительно специальных символов: если «#» заменить более традиционным «/», вам нужно будет экранировать тот, который я написал как «/». В противном случае «@» не должен вызывать у вас проблемы. Конечно, если это так в вашей системе, просто экранируйте их с помощью «\».

person igelkott    schedule 18.03.2013

Учитывая следующие ссылки

$ cd /tmp

$ ls -l link* | sed -e 's/^.*\(link\)/\1/'
link -> /usr/admin/Collect_tests
link1 -> /usr/admin/Collect_tests/[email protected]
link2 -> /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com
link3 -> /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com/List.files.emails.dummy*Printed
link4 -> /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com/List.files.emails.dummy
link5 -> /usr/admin/Collect_tests/[email protected]/

используйте модуль File::Find, как в

$ TARGET_NAME='Upload_Shema@@@.DATA.com' perl -MFile::Find -le 'find sub {
   -l && defined($dst = readlink $_) &&
   index($dst, $ENV{TARGET_NAME}) >= 0 &&
   print "$File::Find::name $dst" }, @ARGV' /tmp
/tmp/link2 /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com
/tmp/link3 /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com/List.files.emails.dummy*Printed
/tmp/link4 /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com/List.files.emails.dummy

Это действительно громоздко, как однострочник. В качестве отдельной команды становится

#! /usr/bin/env perl

use strict;
use warnings;

use File::Find;

die "Usage: $0 root-dir ..\n" unless @ARGV;
die "$0: TARGET_NAME is not defined\n" unless exists $ENV{TARGET_NAME};

sub print_matching_target_name {
  return unless -l && defined(my $dst = readlink $_);
  print "$File::Find::name $dst\n" if index($dst, $ENV{TARGET_NAME}) >= 0;
}

find \&print_matching_target_name, @ARGV;

Пример вывода:

$ find-target
Usage: find-target root-dir ..

$ find-target /tmp
find-target: TARGET_NAME is not defined

$ [email protected] ./find-target /tmp
/tmp/link2 /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com
/tmp/link3 /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com/List.files.emails.dummy*Printed
/tmp/link4 /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com/List.files.emails.dummy
person Greg Bacon    schedule 18.03.2013
comment
можно ли также распечатать файл ссылки перед TARGET_NAME , например: /tmp/link [email protected] - person ; 18.03.2013
comment
почему цель моего вопроса - проверить, соответствует ли значение $ TARGET_NAME в PATH (последнего поля), я не хочу только печатать базовое имя каждого PATH - цель состоит в том, чтобы сопоставить $ TARGET_NAME с выходом (последним поле ПУТЬ ) - person ; 18.03.2013
comment
@Eytan Я понимаю, что ты сейчас ищешь. Смотрите обновленный ответ. - person Greg Bacon; 18.03.2013
comment
спасибо, еще один вопрос, если я хочу также напечатать полное $TARGET_NAME, мне нужно установить это: print $File::Find::name $b $ENV{TARGET_NAME}}, @ARGV' /tmp ??? - person ; 18.03.2013
comment
пример того, что мне нужно: /tmp/test/link2 Upload_Shema@@@.DATA.com /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com - person ; 18.03.2013
comment
@Eytan Полное имя (пункт назначения символической ссылки) является результатом вызова readlink, поэтому вам нужно будет сохранить возвращаемое значение в переменной и распечатать его. - person Greg Bacon; 18.03.2013
comment
можно ли обновить ваш ответ этим, потому что мне не удалось обновить строку perl лайнера - person ; 18.03.2013
comment
Я заметил небольшую проблему, если я установил TARGET_NAME=Collect_tests, тогда я ожидал увидеть 6 совпадающих строк (для всех ссылок ссылка, ссылка1, ссылка2, ссылка3, ссылка4, ссылка5) код печатает только ссылку Замечание Collect_tests - причина, по которой я хочу напечатайте все строки, соответствующие $TARGET_NAME , потому что позже мне нужно будет заново создать новые ссылки - person ; 18.03.2013
comment
вы также можете увидеть вопрос об обновлении - я добавляю новый пример рядом со строкой. Результаты будут отображаться следующим образом. - person ; 19.03.2013
comment
ваш код превосходен (как для ОС Linux, так и для Solaris), но мне все еще нужна ваша помощь по поводу двух последних трех комментариев. - person ; 19.03.2013
comment
Грег, последний вопрос, мне нужно определить другое значение как: export NEW_NAME=’Upload_NEW.Shema.com’, и я хочу напечатать его в трех полях (последнее поле) - person ; 19.03.2013
comment
пример: /tmp/link2 /usr/admin/Collect_tests/[email protected]/Upload_Shema@@@.DATA.com Upload_NEW.Shema.com - person ; 19.03.2013

Попробуйте сделать это:

#!/bin/bash

while IFS= read -r file; do
    printf "TARGET_NAME=%q\n" "$file"
done < <(find /tmp -type l -printf '%l\n')

Результат обозначается обратной косой чертой, например:

TARGET_NAME=/tmp/foo/List.files.emails.dummy\*Printed
person Gilles Quenot    schedule 18.03.2013
comment
Почему не printf 'TARGET_NAME=%q\n' "$file"? Кроме того, зачем две опции '-printf '%l\n' для find? - person Josh Cartwright; 18.03.2013
comment
@Sputnick, почему он печатает \ перед * , вывод должен совпадать с именем каталога/файла - person ; 18.03.2013
comment
Сообщение отредактировано соответственно. Убрана опечатка и добавлено улучшение - person Gilles Quenot; 18.03.2013
comment
@Eytan, это для требования: 4) need to escape special characters as: " / " , " @ " . " * " , etc - person Gilles Quenot; 18.03.2013
comment
Обратите внимание, что если цель ссылки содержит какие-либо символы, такие как встроенная новая строка, вывод printf '%q' будет содержать $'foo\nbar', подобный bash. В требованиях не указано, как поступать в этом случае. - person Josh Cartwright; 18.03.2013
comment
За более чем 10 лет использования Unix я ни разу не видел файла с новой строкой. (Я знаю, что это возможно) - person Gilles Quenot; 18.03.2013
comment
@sputnick хорошо ... Я использовал новую строку в качестве примера, но любой непечатаемый символ страдает от этой же проблемы. Вы когда-нибудь видели использование имен файлов в кодировке UTF-8? - person Josh Cartwright; 18.03.2013