Единственные варианты расширения команд bash:

  • без кавычек $(..) чьи выходные данные всегда анализируются с разбивкой на немые строки
  • процитировал "$(..)" чей вывод всегда передается как единица?

Я пытаюсь повторить в bash функцию рыбной раковины, которую я создал для использования в Mac OS. Моя функция selection рыбы https://superuser.com/a/1165855 берет выборку в переднем окне и выводит пути для использования в подстановке команд, такие как ls -l (selection) . Я надеялся добиться того же в bash, возможно, как ls -l $(selection) .

Я подумал, что это вопрос кавычек, и поэтому попытался передать пути, разделенные переводом строки, в printf "%q " bash" bash. Однако я обнаружил, что независимо от того, в какую цитату я поместил вывод подстановки команд, он разделялся на пустые места.

Упрощенный пример:

$ touch file\ a file\ b
$ ls $( echo "file\ a file\ b" ) # want expansion equiv to: ls 'file a' 'file b'
ls: a: No such file or directory
ls: b: No such file or directory
ls: file\: No such file or directory
ls: file\: No such file or directory

Это был бы не конец света, если бы мне пришлось использовать подстановку команд в кавычках, например, ls -l "$(selection)" но при этом вывод команды никогда не разделялся, не обращая внимания на мое тщательное цитирование. Старый синтаксис backtick отличается?

Забавно, Баш, у тебя много возможностей. Неужели никто не разрешил использовать cmda $(cmdb-that-generates-parameters-for-cmda)? Или пользователь bash просто избегает пробелов или символов в именах файлов (например, животных), чтобы все было легко? Спасибо за любые ответы.

2 ответа2

2

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

Вариант № 1

Использование специальной внутренней переменной IFS (Internal Field Separator)

#!/bin/bash

touch 'file a' 'file b'

# Set IFS to new line as a separator
IFS='
'
ls -la $( echo 'file a'; echo 'file b' )

Вариант № 2

Использование for цикла

#!/bin/bash

touch 'file a' 'file b'
for variable in 'file a' 'file b';do
  ls -la "${variable}"
done

Вариант № 3

Использование цикла for из предопределенных значений

#!/bin/bash

MultiArgs='
arg 0 1
arg 0  2
arg 0   3
arg 0    N
'

# Using only new line as a separator
IFS='
'

for variable in ${MultiArgs};do
  touch "${variable}"
  ls -la "${variable}"
done

Вариант № 4

Использование ~ (тильда) в качестве разделителя

#!/bin/bash

MultiArgs='arg 0 1~arg 0 2~arg0 3~ar g 0 N' # Arguments with spaces

IFS='~'
for file in ${MultiArgs};do
 touch "${file}"
 ls -la "${file}";
done

Старый синтаксис backtick отличается?

Нет, это то же самое, но обратные пометки имеют некоторые ограничения, которых нет у $()

Или пользователь bash просто избегает пробелов или символов в именах файлов (например, животных), чтобы все было легко?

Нет, нормально использовать пробелы в именах файлов, если использовать правильные кавычки.

Как насчет $(cmdb-that-generates-parameters-for-cmda)

#!/bin/bash

# first command generate text for `sed` command that replacing . to ###
echo $( for i in {1..5}; do echo "${i} space .";done | sed 's/\./###/g' )

#############################################
# Another example: $() inside of another $()
for i in {1..5}; do touch "${i} x.file";done # Prepare some files with spaces in filenames

IFS='
'
echo "$( ls -la $(for i in ls *.file; do echo "$i"; done))"

Если вы хотите передать все параметры в одну строку, чтобы передать transcode вашей программы, вы можете добавить в конец вашего файла ~/.bashrc следующую функцию:

_tr() {
  local debug
  debug=1
  [ $debug -eq 1 ] && {
    echo "Number of Arguments: $#"
    echo "Arguments: $@"
  }

  transcode "$@"
}

и вызвать эту функцию из командной строки следующим образом:

eval _tr $(echo "$out")

Где переменная out должна быть такой: out="'file a' 'file b'" .
Если бы вводить имена файлов вручную, то вызов функции _tr может выглядеть так:

eval _tr $(echo "'file a' 'file b'")

Если вы используете какой-то внешний скрипт вместо $() внешний скрипт / программа должен вернуть список файлов, цитируемых в точности так:

"'file a' 'file b'"
1

С xargs:

echo '"file a" "file b"' | xargs ls

Следите за цитатами. Обратите внимание, что xargs не является встроенным в Bash (я упоминаю об этом в контексте вашего «Bash, у вас много возможностей»).

С eval который является встроенным в Bash:

eval ls $( echo '"file a" "file b"' )

Опять же, обратите внимание на цитаты. Это может иметь неприятные последствия. Методы, основанные на изменении IFS (этот другой ответ), кажутся более безопасными. С другой стороны, с eval вы даже можете сделать это:

eval ls $( echo '"file "{a,b}' )

К сожалению, ни один из них не так прост, как синтаксис ls (selection) другой оболочки, с которого вы начали. Однако вы можете упростить синтаксис любого (?) решение с пользовательской функцией, например:

_(){ "$2" | xargs "$1"; }

Это позволяет вам вызывать _ ls selection , если только selection генерирует ввод для xargs right. Еще лучше, это:

_(){ "${@:$#}" | xargs "${@:1:$(($#-1))}"; }

делает возможным _ ls -l "file c" selection . Я думаю, вы могли бы создать аналогичную функцию, которая использует метод "изменить IFS " из другого ответа.

Тем не менее, это не так гибко и элегантно, как у рыбных ls (selection) . Я ожидаю, что вы можете сделать ls (selection1) (selection2) в рыбе; вышеуказанная функция _ не охватывает этот случай.

С другой стороны, eval ls $(selection1) $(selection2) может работать, если selection1 и selection2 возвращают строки, очищенные с помощью правильных кавычек и / или экранирования. Если злоумышленник может заставить вас выбрать файл с именем ; rm -rf ~/; тебе лучше хорошо продезинфицировать; или не использовать eval в первую очередь.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .