Как мне выполнить цикл по строкам в STDIN и выполнить команду оболочки?

Как мне выполнить цикл по строкам в STDIN и выполнить команду оболочки?

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

В этом случае я хотел бы запустить xargs mv. Например, даны две строки:

mfoo foo
mbar bar

Я хотел бы баллотироваться:

xargs mv mfoo foo
xargs mv mbar bar

Я пробовал следующие стратегии с ruby, awk, и xargs. Однако я делаю это неправильно:

Только xargs:

$ echo "mbar bar\nmbaz baz" | xargs mv
usage: mv [-f | -i | -n] [-v] source target
       mv [-f | -i | -n] [-v] source ... directory

Через awk:

$ echo "mbar bar\nmbaz baz" | awk '{ system("xargs $0") }'

Через ruby:

$ echo "mbar bar\nmbaz baz" | ruby -ne '`xargs mv`'
$ ls
cat  foo  mbar mbaz

У меня есть несколько вопросов:

  • Как мне сделать то, что я пытаюсь сделать?
  • Что не так с каждой из моих попыток?
  • Есть ли лучший способ «подумать» о том, что я пытаюсь сделать?

Меня особенно смущает то, что моя xargsпопытка не срабатывает, поскольку следующее работает:

$ echo "foo\nbar" | xargs touch
$ ls
bar foo

решение1

echo "mbar bar\nmbaz baz" | xargs mv

С xargsвы должны использовать -tопцию, чтобы увидеть, что происходит. Так что в вашем случае выше, если мы вызовем xargsс -t, что мы увидим:

mv mbar bar mbaz baz

Так что, очевидно, это не правильно. Произошло то, что, xargsкак голодный крокодил, съел все аргументы, которые ему передали через канал echo. Поэтому вам нужен способ ограничить передачу аргументов кроку. И поскольку вы запросили построчно, то вам нужна опция -lили -Lдля POSIX.

echo "mbar bar\nmbaz baz" | xargs -l -t mv

Способ в стиле POSIX:

echo "mbar bar\nmbaz baz" | xargs -L 1 -t mv

mv mbar bar
mv mbaz baz

И это то, что вы хотели. HTH

решение2

Вы можете пойти следующим путем:

echo -e "foo bar\ndoo dar" | xargs -n 2 mv

Чтобы прочитать из файла:

cat lines.txt | xargs -n 2 mv

или

xargs -a lines.txt -n 2 mv

решение3

xargsможет вести себя не так, как вы ожидаете. См. комментарий @MichaelHomer к этому вопросу:

xargs mv mfoo fooбудет ждать ввода и запускать mv mfoo foo $x_1 $x_2 $x_3...для каждой $x_nполученной строки ввода. @MichaelHomer

В этом можно убедиться, прочитавxargsстраница руководства:

ОПИСАНИЕ
Утилита xargs считывает строки, разделенные пробелами, табуляциями, символами новой строки и конца файла, из стандартного ввода и запускает утилиту со строками в качестве аргументов.

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

$ echo "foo bar\ncat dog"
foo bar
cat dog
$ echo "foo bar\ncat dog" | xargs echo
foo bar cat dog

Это поведение можно контролировать с помощью-nфлаг:

-n число

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

Возвращаясь к предыдущему примеру, если мы хотим xargsвзять только 2 аргумента и выйти, то мы можем использоватьПодход @ddnomad:

$ echo "foo bar\ncat dog" | xargs -n 2 echo
foo bar
cat dog

Также, как показано в комментариях к ответу Ракеша Шармы, на BSD xargsвы можете указать количество строк для чтения с помощью -Lфлага. Из страницы руководства:

-L номер

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

Не менее полезным для тестирования является -tпереключатель:

Возвращаясь к нашему примеру, оба следующих варианта будут работать; однако второй пример лучше, поскольку именно это и имелось в виду в данном случае:

$ echo "foo bar\ncat dog" | xargs -n 2 echo
foo bar
cat dog
$ echo "foo bar\ncat dog" | xargs -L 1 echo
foo bar
cat dog

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

Параметры -L и -n являются взаимоисключающими; будет использоваться последний указанный параметр.

решение4

Есть очень простое решение, которое не использует xargs.
Определим эту функцию:

$ mv2() { while (($# >1 )); do echo mv "$1" "$2"; shift 2; done; }

Затем просто вызовите его со списком необходимых имен файлов (переводы строк не требуются):

$ mv2 mbar bar mbaz baz
mv mbar bar
mv mbaz baz

Уберите эхо, чтобы функция действительно заработала.

Связанный контент