2017-01-19 5 views
1

В Bash, v.4.3.11 (1) У меня есть этот пример кода:Bash один вкладыш для выделения имени каталога

#!/bin/bash 

dir=("/home/user/Documents/" "/home/user/Music/" "/home/user/Videos/" \ 
"/home/user/Photos/") 

baseDir="/home/user/" 

for i in "${!dir[@]}" 
do 
    niceName=${dir[$i]#"$baseDir"} # removes baseDir from front part 
    printf "%s\n" "${niceName%"/"}" # also removes trailing slash from end 
done 

Есть ли способ объединить две команды в одном и иметь только printf в цикле for? (желательно, не прибегая к awk или sed, но нормально, если это неизбежно).

Я пробовал различные комбинации, но я заканчиваю ошибки «плохой замены». Например, printf "%s\n" "${niceName=${dir[$i]#"$baseDir"%"/"}" не работает для меня.

+3

Есть ли какой-либо причине вы используете 'для I в' и '$ {реж [$ я]} 'вместо' for i in '$ {dir [@]} "' и просто '$ i'? –

+1

Вам не нужна обратная косая черта в определении массива; новые строки работают так же, как и пространства для разделения элементов массива. – chepner

+0

Benjamin W: необходимо было легко получить индекс, но вы правы в общем случае. Хепнер: полезные наблюдения, спасибо! – knedas

ответ

3

Простейшая версия.

#!/bin/bash 
dir=("/home/user/Documents/" "/home/user/Music/" "/home/user/Videos/" "/home/user/Photos/") 

for d in "${dir[@]}" 
do 
    basename "$d" 
done 

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

Внутри цикла также может быть заменены встроенными командами на основе решений ниже (быстрее и без внешних зависимостей):

d="${d%/}" 
    echo "${d##*/}" 
+0

'basename' существенно неэффективен по сравнению с аналогичным расширением. '$ {d ## * /}' намного, * много * быстрее оценивается, потому что он встроен в оболочку, тогда как 'basename' является отдельным исполняемым файлом. –

+1

Он выполняется намного быстрее и будет правильным выбором в контексте, когда скорость имеет значение или для кого-то знающего, кто пишет это из памяти. В этом смысле это лучше. Это сложнее вспомнить, поэтому, для большинства людей решение, которое легче всего запомнить, может быть более быстрым, если вы включите время, необходимое для его поиска. – Fred

+0

Если тот факт, что он уже помнит, является достаточным основанием для сохранения дурной привычки, эта привычка никогда не будет нарушена. Производительность - это только один аспект - другой - это надежность: Shell builtins не будет терпеть неудачу, если недействительный PATH, плохой LD_LIBRARY_PATH или риск отсутствует при запуске на встроенной платформе, которая была удалена в кости. Большая часть моей говядины заключается в том, что сценарий оболочки имеет репутацию врожденной медленности, которая во многом обязана общепринятой идиоме и практике, чем технические ограничения; если вы посмотрите на обоснование для systemd, «скрипты оболочки неэффективны» были большой частью. –

1

Вы можете использовать basename, который возвращает только имя базового файла. В этом случае ваш файл является каталогом.

#!/bin/bash 
dir=("/home/user/Documents/" "/home/user/Music/" "/home/user/Videos/" "/home/user/Photos/") 

for i in "${!dir[@]}" 
do 
    echo $(basename ${dir[$i]}) 
done 
+3

Использование подстановки подстановки $() для захвата вывода команды, а «эхо» на этом выходе является избыточным. «basename» уже перекликается с его выходом. Кроме того, аргумент basename должен быть указан. – Fred

+0

+1 для предложения 'basename', но Fred прав насчет исправлений, и это не касается общей проблемы расширения двойных параметров, потому что это работает только с dirs. – knedas

2

Если вы ищете однострочник используя замену, это ll work:

dir=("/home/user/Documents/" "/home/user/Music/" "/home/user/Videos/" \ 
"/home/user/Photos/") 
baseDir="/home/user/" 
dir=("${dir[@]%/}") ## This line removes trailing forward slashes 
printf "%s\n" "${dir[@]#$baseDir}" 
+0

Кстати, использование OP 'printf '% s \ n'' вместо' echo', вероятно, является хорошим вызовом здесь - оно отличает 'foo bar' и' 'foo bar" '. Этот ответ * корректен, но нельзя однозначно определить, что из вывода echo, потому что вывод неразличим между '' $ {dir [*]} "' -style и '" $ {dir [@]} "' -этажные расширения. –

+0

Вы правы! Я забыл об этом и отредактировал его, чтобы вести себя правильно и поместить каждый на свою линию. Кроме того, если по какой-то причине вы хотели, чтобы он работал с эхом, вы могли получить ту же функциональность, используя '(IFS = $ '\ n'; echo" $ {dir [*] # $ baseDir} ")', чтобы напечатать их , –

+0

Ваш код не удаляет косые черты для меня. – knedas

1

Насколько я знаю, bash не обрабатывает «расширение двойного параметра» (я считаю, что zsh делает). Тем не менее, вы можете взломать вместе решение так: "{! Реж [@]} $"

dir=("/home/user/Documents/" "/home/user/Music/" "/home/user/Videos/" "/home/user/Photos/") 
trunk=/home/user/ 

echo $(dir=("${dir[@]##${trunk}}"); echo "${dir[@]%/}") 
+0

Цитаты важны - 'dir = ($ {foo [@]})' не то же самое, что 'dir = (" $ {foo [@]} ") '- играйте с именами, имеющими пробелы. –

+0

... кстати, «имена, имеющие пробелы» (или символы новой строки или, возможно, буквально любые другие символы, отличные от NUL), почему этот подход небезопасен для использования: единственный способ, которым вы можете безопасно хранить элементы массива отдельно друг от друга, который работает со всеми возможными именами файлов является NUL-delimit их, но NULs в подстановке команд молча удаляются. –

+0

(Обратите внимание, что «все возможные имена файлов» даже содержат символы новой строки, поэтому вы не можете доверять этим). –

Смежные вопросы