Bash: Передача команды с параметрами в кавычках функции

Короче говоря, rsync записывает удвоенное количество байтов, потому что порождает два процесса / потока для сделайте копию, и между процессами будет один поток данных, а другой - от принимающего процесса в целевой файл.

Мы можем сказать это, посмотрев более подробно на вывод strace , идентификаторы процессов в начале файла, а также номера файловых дескрипторов в вызовах write . использоваться, чтобы отличать разные «потоки» записи друг от друга.

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


Использование чего-то вроде strace -e trace = process, socketpair, open, read, write покажет некоторые порожденные потоки, пару сокетов, создаваемую между ними, и разные потоки, открывающие входные и выходные файлы. .

Тестовый прогон, аналогичный вашему:

$ rm test2
$ strace -f -e trace=process,socketpair,open,close,dup,dup2,read,write -o rsync.log rsync -avcz --progress test1 test2
$ ls -l test1 test2
-rw-r--r-- 1 itvirta itvirta 81920004 Jun 21 20:20 test1
-rw-r--r-- 1 itvirta itvirta 81920004 Jun 21 20:20 test2

Давайте посчитаем количество байтов, записанных для каждого потока отдельно:

$ for x in 15007 15008 15009  ; do echo -en "$x: " ; grep -E "$x (<... )?write"  rsync.log | awk 'BEGIN {FS=" = "} {sum += $2} END {print sum}'  ; done 
15007: 81967265
15008: 49
15009: 81920056

Что в значительной степени соответствует теории, приведенной выше. Я не проверял, какие еще 40 КБ были записаны первым потоком, но я предполагаю, что он распечатывает результат выполнения и любые метаданные о синхронизированном файле, которые rsync необходимо передать на другой конец.


Я не проверял, но предполагаю, что даже при включенном дельта-сжатии, возможно, «удаленный» конец rsync все еще записывает (большую часть) файла полностью, что приводит к примерно тому же количеству записей, что и с cp. Передача между потоками rsync меньше, но конечный результат остается прежним.

3
05.03.2016, 23:16
3 ответа

Возможно, вы не захотите передавать всю команду в виде строки. У нас есть списки в оболочке в виде списка аргументов, и передача списка в виде списка намного проще.

Вместо exe blah «blahh cmd» вы пишете команду прямо как exe blah blahh cmd . Затем, когда вам нужно использовать всю команду напрямую, используйте расширение среза , чтобы получить все после первого аргумента: ERROR = $ ("$ {@: 1}" 2> & 1) .

Традиционно люди могут использовать shift для смещения всего списка аргументов «влево» (см. help shift ):

f(){
    local j="$1"
    shift
    echo "$j,$3"
    shift 50
    echo "$1" # guess what "$@" is now?
}

f {1..100}

Но это не обязательно для bash аппаратно.

Говоря о нарезке, вы также можете проверить массивы в bash.


Ох уж ... Вы можете использовать eval для непосредственного запуска строки, но это часто считается плохим, поскольку в этом случае вы разрешаете гораздо больше, чем простые команды.


И в качестве подсказки стиля предпочтите более короткую и более переносимую (POSIX-) xxx () , а не функцию xxx и функцию xxx () . В bash они просто идентичны.

2
27.01.2020, 21:22

Основная проблема вашего вопроса заключается в том, «как разбить строку» внутри $ var .

«Злой» (потому что он подвержен ошибкам и выполнению кода) - использовать eval:

 eval set -- $var           ### Dangerous, not recommended, do not use.

Это устанавливает разделенную строку в позиционных аргументах (массив немного сложнее). Но из-за того, что переменная $ var не заключена в кавычки (чего следует избегать любой ценой, если вы действительно не знаете, что делаете), она подвержена «разбиению слов» (что мы хотим), но это также позволяет, расширение "случается. Вы можете попробовать эти команды (используйте каталог с несколькими файлами)

$ var='hello * world'
$ eval set -- $var
$ echo "$@"

Выполнение безопасно, нет никаких внешних установленных значений, и расширение * просто установит значения в позиционных параметрах.

Чтобы избежать «расширения имени пути», используется set -f , и в этом случае его легко интегрировать в команду:

$ var='hello * world'
$ set -f
$ eval set -- $var
$ echo "$@"
hello * world

То есть с IFS по умолчанию пробел Вкладка Новая строка .

Все могло бы стать сложным, если бы IFS можно было установить извне.

Некоторые проблемы можно решить, используя read :

$ IFS=' ' read -ra arr <<<"$var"
$ echo "${arr[@]}"
hello * world

Это устанавливает IFS для команды (избегает внешней установки IFS), читает без обработки обратной косой черты (опция -r), помещает все в массив переменная (параметр -a) и использует переменную в кавычках «$ var» . Единственное предостережение: повторяющиеся пробелы между словами будут удалены (из-за того, что IFS является пробелом). Это не проблема для исполняемой командной строки.

Но попытка выполнить команды, которым нужны аргументы с пробелами, потерпит неудачу:

$ var='date -d "-1 day" +"%Y.%m.%d-%H:%M:%S"'
$ IFS=' ' read -ra arr <<<"$var"
$ "${arr[@]}"
date: extra operand `+"%Y.%m.%d-%H:%M:%S"'

Единственное реальное решение - правильно построить массив команды с самого начала:

$ arr=( date -d "-1 day" +"%Y.%m.%d-%H:%M:%S" )
$ "${arr[@]}"
2016.03.05-00:25:17

Думайте об этом решении как о CSV "Запятая ( пробел) Значения с разделителями ».

Этот скрипт будет работать:

#!/bin/bash

function exe {
    echo "Execute: $1"
    # Loops every 3s, outputting '...' until command finished executing
    LOOP=0
    while true; do
        if [ $LOOP -gt 0 ]; then echo -e "..."; fi;
            sleep 3;
            (( LOOP++ ))
    done &

    ERROR="$("${@:2}" 2>&1)" # Execute command and capture output.
    status=$?

    kill $!; trap 'kill $!' SIGTERM

    if [ $status -ne 0 ];
    then
        echo "✖ Error" >&2
        echo "$ERROR" >&2
    else
        echo "✔ Success"
    fi
    return $status
}

cmd=( date -d '-1 day' +'%Y.%m.%d-%H:%M:%S' )
exe "give me yesterday date" "${cmd[@]}" 

cmd=( sudo apt-get update )
exe "update package list" "${cmd[@]}" 
1
27.01.2020, 21:22

В случае кавычек в строках параметров, которые должны выполняться в виде кода, можно повторно проанализировать строку параметров в массив, такой как массив позиционных параметров $ @ . Это может быть достигнуто - по крайней мере, для данного примера - с помощью ... & ERROR = "$ (printf"% s "" $ 2 "| xargs sh -c 'exec" $ 0 "" $ @ "2> & 1') ... . (Есть случаи с дополнительными двойными кавычками в этой уже заключенной в кавычки строке, которые могут вызывать сообщения xargs: unterminated quote ).

Дополнительные предложения см. В: Linux / Bash: Как отменить цитирование? .

# test cases
# help :
#set -- '' "ls -ld / 'a bc'" 
set -- '' ": sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/apache2/ssl/apache.key -out /etc/apache2/ssl/apache.crt -subj \"/C=GB/ST=London/L=London/O=Company Ltd/OU=IT Department/CN=dev.domain.local\""

printf "%s" "$2" | 
    xargs sh -c '
       echo "arg 0: ${0}"
       for ((i=1; i<=$#; i++)); do
          echo "arg $i: ${@:i:1}"
       done
       set -xv
       "$0" "$@"
    ' 

# output
arg 0: :
arg 1: sudo
arg 2: openssl
arg 3: req
arg 4: -x509
arg 5: -nodes
arg 6: -days
arg 7: 365
arg 8: -newkey
arg 9: rsa:2048
arg 10: -keyout
arg 11: /etc/apache2/ssl/apache.key
arg 12: -out
arg 13: /etc/apache2/ssl/apache.crt
arg 14: -subj
arg 15: /C=GB/ST=London/L=London/O=Company Ltd/OU=IT Department/CN=dev.domain.local
   "$0" "$@"
+ : sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/apache2/ssl/apache.key -out /etc/apache2/ssl/apache.crt -subj '/C=GB/ST=London/L=London/O=Company Ltd/OU=IT Department/CN=dev.domain.local'

LOOP = $ LOOP + 1 в приведенном выше коде должно быть LOOP = $ ((LOOP + 1)) кстати.)

0
27.01.2020, 21:22

Теги

Похожие вопросы