Я бы сделал так:
#! /bin/sh -
pattern=${1?Please provide a file name}
shift
[ "$#" -gt 0 ] || set /
find "$@" -iname "$pattern" | grep '^' && exit
echo >&2 "Not found"
exit 1
То есть, передайте name (обратите внимание, что find
рассматривает его как (без учета регистра из-за -iname
) шаблон для поиска имен файлов, а не как точное имя файла для поиска) в качестве первого аргумента. Все остальные аргументы - это каталоги или файлы для поиска, а если ни один из них не указан, то поиск будет производиться в /
.
Вместо того чтобы хранить вывод в переменной и выводить его в конце, используйте grep
в качестве сквозной функции, которая сообщает true, если файл найден.
Также я вывожу сообщение "Not found" на stderr вместо stdout и сообщаю о невозможности найти файл в статусе выхода.
Как отметил @ThomasN, существует обычная проблема, заключающаяся в том, что вы не можете надежно передавать имена файлов/каталогов в find
. Если вы хотите искать в каталоге с именем -delete
, вызов that-script name -delete
, например, приведет к катастрофическим последствиям. В BSD find
это можно обойти, сделав (перед вызовом find
):
for i do
set -- "$@" -f "$i"
shift
done
Переносимо (хотя обратите внимание, что -iname
не переносимо), вам нужно будет добавить . /
к относительным путям, что может быть проблематично, например:
for i do
case $i in
(. | ./* | /*) ;; # /foo, ./foo are not a problem
(*) i=./$i
esac
set -- "$@" "$i"
shift
done
find "$@"...
Однако это влияет на вывод (например, вы увидите ./delete/name
вместо -delete/name
).
Подход grep(требуется поддержка PCRE):
s="This is a test some stuff I want string junk string end"
grep -Po 'te.*?ng' <<< $s
Альтернативный подходperl :
perl -ne 'print "$&\n" if /te.*?ng/' <<< $s
Вывод (для обоих подходов):
test some stuff I want string
.*?
- ?
вот нежадныймодификатор, указывает соответствие в минимальном модеsteeldriver правильно указал на нежадное совпадение с регулярным выражением SED (эмулировать perl's .*?), где John1024 ясно указывает:
регулярные выражения Sed соответствуют самому длинному совпадению. У Sed нет эквивалента нежадности.
Таким образом, есть два альтернативных способа обойти проблему. Во-первых, используйте то, что на самом деле имеет нежадное сопоставление, например perl:
$ str="This is a test some stuff I want string junk string end"
$ perl -pe 's/^.*(te.*?ng).*/\1/' <<< "$str"
test some stuff I want string
В качестве альтернативы вы можете дать sed больше контекста для группировки совпадений, т.е. добавить то, что будет следовать за первым "строковым" словом:
$ sed -r 's/^.*(te.*?ng)\ junk.*/\1/' <<< "$str"
test some stuff I want string
Сделайте это в два шага: сначала удалите префикс (в случае, если терминатор присутствовал в префиксе), затем удалите все после префикса. Используйте команду T
, чтобы пропустить строку, если она не совпадает:
echo "This is a test some stuff I want string junk string end" |
sed -n 's/.*\(.te.*ng\)/\1/; T; s/\(ng\).*/\1/p'
Можно также сначала удалить не совпадающие строки, а затем выполнить замену на досуге.
echo "This is a test some stuff I want string junk string end" |
sed '/.*\(.te.*ng\)/!d; s/.*\(.te.*ng\)/\1/; s/\(ng\).*/\1/'
В качестве альтернативы выполните замену и окончательную печать только на совпадающих строках.
echo "This is a test some stuff I want string junk string end" |
sed '/.*\(.te.*ng\)/ { s/.*\(.te.*ng\)/\1/; s/\(ng\).*/\1/p; }'
Я бы предложил использовать команду cut в вашем случае
echo "I am a useful and I am a string. Did I mention that I'm a string?" | cut -d "string" -f1
Это разрезало бы строку на три части (перед первой, после 2. И между «строкой») с -d"" вы можете выбрать шаблон, который вы хотите использовать в качестве резака, а с -fNumber вы выбираете, какую часть взять. Проблема: "строка" будет удалена Решение:
String=`echo "I am a useful and I am a string. Did I mention that I'm a string?" | cut -d "string" -f1`
String="$(String) string"
echo $String
Он добавляет разделитель "string", который был удален, в конец переменной $String, которая была определена в выходных данных