Есть ли способ заставить «мв» потерпеть неудачу молча?


61

Команда like mv foo* ~/bar/выдает это сообщение в stderr, если не найдено ни одного файла foo*.

mv: cannot stat `foo*': No such file or directory

Тем не менее, в сценарии, над которым я работаю, этот случай вполне подойдет, и я хотел бы опустить это сообщение в наших журналах.

Есть ли хороший способ сказать, mvчтобы быть тихим, даже если ничего не было перемещено?


13
mv foo* ~/bar/ 2> /dev/null?
Томас Найман

1
Ну, да. Я думаю, что я имел в виду что-то «более приятное», например, переключатель mv. :) Но это будет делать, конечно.
Джоник

3
Я видел, что некоторые mvреализации поддерживают -qопцию, чтобы успокоить их, но это не является частью спецификации POSIX для mv. mvВ GNU Coreutils, например, вовсе не имеют такой вариант.
Томас Найман

Ну, я не думаю, что проблема в том, как сохранить "mv" в покое, но как сделать это более правильно. Проверяя, есть ли файл / dir foo *, и есть ли у пользователя разрешение на запись, наконец, возможно, выполните «mv»?
Shâu Shắc

Ответы:


47

Вы ищете это?

$ mv  file dir/
mv: cannot stat file’: No such file or directory
$ mv  file dir/ 2>/dev/null
# <---- Silent ----->

20
обратите внимание, возвращаемое значение все еще! = 0, поэтому любой set -e(который должен использоваться в каждом сценарии оболочки) потерпит неудачу. Вы можете добавить, || trueчтобы отключить проверку для одной команды.
Reto

10
@reto set -eне следует использовать в каждом сценарии оболочки, во многих случаях это делает управление ошибками еще более сложным, чем без него.
Крис Даун

1
@ChrisDown Я понимаю вашу точку зрения. Обычно это выбор между двумя пороками. Но если есть сомнения, я предпочитаю неудачный успешный забег, чем успешный провал. (который не будет замечен, пока не станет видимым для клиента / пользователя). Сценарии оболочки - это большая шахта для начинающих, так легко пропустить ошибку, и ваш сценарий потерпел неудачу!
Reto

14

На самом деле, я не думаю, что отключение звука mvявляется хорошим подходом (помните, что он может сообщать вам и о других вещах, которые могут представлять интерес ... например, отсутствующие ~/bar). Вы хотите отключить звук только в том случае, если ваше глобальное выражение не возвращает результатов. На самом деле, а не выполнять его вообще.

[ -n "$(shopt -s nullglob; echo foo*)" ] && mv foo* ~/bar/

Не выглядит очень привлекательно, и работает только в bash.

ИЛИ ЖЕ

[ 'foo*' = "$(echo foo*)" ] || mv foo* ~/bar/

только кроме вас в bashс nullglobнабором. Вы платите цену за 3-хкратное повторение образца шара.


Незначительная проблема со второй формой: не удается переместить файл с именем, foo*если он является единственным в текущем каталоге, который соответствует глобу foo*. Это можно обойти с помощью выражения glob, которое буквально не соответствует самому себе. Например [ 'fo[o]*' = "$(echo fo[o]*)" ] || mv fo[o]* ~/bar/.
фра-сан

13

find . -maxdepth 1 -name 'foo*' -type f -print0 | xargs -0r mv -t ~/bar/

- GNU mvимеет хороший параметр "destination first" ( -t) и xargsможет пропустить выполнение своей команды, если нет ввода вообще ( -r). Использование -print0и -0соответственно гарантирует, что не будет беспорядка, когда имена файлов содержат пробелы и другие "забавные" вещи.


2
Следует отметить , что -maxdepth, -print0, -0и -rтакже GNU расширения (хотя некоторые из них находятся в других реализациях в настоящее время).
Стефан Шазелас

1
Пойдж, многие наши пользователи не используют Linux. Это стандартная практика и очень полезно указывать, какие части ответа не являются переносимыми. Сайт называется « Unix & Linux», и многие люди используют те или иные формы BSD или Unix или AIX, или macOS, или встроенные системы. Не принимай это на свой счет.
Тердон

Я не принимаю ничего лично. Хотя у меня есть мнение, что вы удалили, как я вижу сейчас. Поэтому я повторяю: я нахожу эти комментарии "заметьте, что это GNU" довольно бессмысленными. Их вообще не стоит добавлять, во-первых, поскольку мой ответ ясно указывает на то, что он основан на версии GNU mv.
poige

5

Важно понимать, что на самом деле это оболочка, которая расширяет ее foo*до списка совпадающих имен файлов, поэтому мало что mvможет сделать сам.

Проблема здесь в том, что когда глобус не совпадает, некоторые оболочки bash(и большинство других подобных Борну оболочек, такое ошибочное поведение было фактически введено оболочкой Борна в конце 70-х) дословно передают шаблон в команду.

Таким образом, здесь, когда foo*не совпадает ни один файл, вместо прерывания команды (как это делают оболочки до Борна и несколько современных оболочек), оболочка передает дословный foo*файл mv, поэтому в основном запрашивается mvперемещение вызываемого файла foo*.

Этот файл не существует. Если бы это было так, это действительно соответствовало бы шаблону, поэтому mvвыдает ошибку. Если шаблон был foo[xy]вместо этого, mvмог бы случайно переместить файл с именем foo[xy]вместо fooxи fooyфайлов.

Теперь, даже в тех оболочках, у которых нет этой проблемы (pre-Bourne, csh, tcsh, fish, zsh, bash -O failglob), вы все равно получите сообщение об ошибке mv foo* ~/bar, но на этот раз от оболочки.

Если вы хотите считать это не ошибкой, если нет соответствия файлов foo*и в этом случае ничего не перемещать, сначала нужно создать список файлов (таким образом, чтобы не вызывать ошибку, например, с помощью nullglobпараметра некоторые снаряды), и тогда только вызов mvэто список не пустой.

Это было бы лучше, чем скрывать все ошибки mv(как при добавлении 2> /dev/null), как если бы произошел mvсбой по любой другой причине, вы, вероятно, все равно захотите узнать, почему.

в зш

files=(foo*(N)) # where the N glob qualifier activates nullglob for that glob
(($#files == 0)) || mv -- $files ~/bar/

Или используйте анонимную функцию, чтобы избежать использования временной переменной:

() { (($# == 0)) || mv -- "$@" ~/bar/; } foo*(N)

zshэто одна из тех оболочек, которые не имеют ошибки Борна и сообщают об ошибке, не выполнив команду, когда глобус не совпадает (и nullglobопция не была включена), поэтому здесь вы можете скрыть zshошибку и восстановить для stderr, mvтак что вы все равно увидите mvошибки, если они есть, но не ошибки о несоответствующих глобусах:

(mv 2>&3 foo* ~/bar/) 3>&2 2>&-

Или вы можете использовать, zargsчто также позволит избежать проблем, если foo*глобус будет расширяться до слишком больших файлов.

autoload zargs # best in ~/.zshrc
zargs -r -- foo* -- mv -t ~/bar # here assuming GNU mv for its -t option

В кш93:

files=(~(N)foo*)
((${#files[#]} == 0)) || mv -- "${files[@]}" ~/bar/

В Баш:

bashне имеет синтаксиса для включения только nullglobдля одного глобуса, и failglobопция отменяется, nullglobпоэтому вам понадобятся такие вещи, как:

saved=$(shopt -p nullglob failglob) || true
shopt -s nullglob
shopt -u failglob
files=(foo*)
((${#files[@]} == 0)) || mv -- "${files[@]}" ~/bar/
eval "$saved"

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

(
  shopt -s nullglob
  shopt -u failglob
  files=(foo*)
  ((${#files[@]} == 0)) || mv -- "${files[@]}" ~/bar/
)

В yash

(
  set -o nullglob
  files=(foo*)
  [ "${#files[@]}" -eq 0 ] || mv -- "${files[@]}" ~/bar/
)

В fish

В оболочке fish поведение nullglob является значением по умолчанию для setкоманды, поэтому оно просто:

set files foo*
count $files > /dev/null; and mv -- $files ~/bar/

POSIXly

Там нет nullglobопции в POSIX shи нет массива, кроме позиционных параметров. Есть способ, который вы можете использовать для определения соответствия шара или нет:

set -- foo[*] foo*
if [ "$1$2" != 'foo[*]foo*' ]; then
  shift
  mv -- "$@" ~/bar/
fi

Используя как a, так foo[*]и foo*glob, мы можем различить случай, когда нет соответствующего файла, и случай, когда есть один файл, который вызван foo*(что set -- foo*не может сделать).

Больше чтения:


3

Возможно, это не самый лучший вариант, но вы можете использовать findкоманду, чтобы проверить, пуста ли папка или нет:

find "foo*" -type f -exec mv {} ~/bar/ \;

1
Это даст буквальный foo*путь поиска find.
Кусалананда

3

Я предполагаю, что вы используете bash, потому что эта ошибка зависит от поведения bash для расширения непревзойденных глобусов для себя. (Для сравнения, zsh выдает ошибку при попытке развернуть бесподобный глобус.)

Итак, как насчет следующего обходного пути?

ls -d foo* >/dev/null 2>&1 && mv foo* ~/bar/

Это будет молча игнорировать, mvесли происходит ls -d foo*сбой, в то время как все еще регистрирует ошибки, если ls foo*успешно, но происходит mvсбой. (Остерегайтесь, ls foo*может произойти сбой по другим причинам, кроме foo*несуществующих, например, из-за недостаточных прав, проблем с FS и т. Д., Поэтому такие условия будут игнорироваться этим решением.)


Да, в основном меня интересует bash (и я пометил вопрос как bash). +1 за то, что учли тот факт, что mvможет не получиться по другим причинам, кроме foo*несуществующих.
Джоник

1
(На практике я, вероятно, не буду использовать это, так как это довольно многословно, и смысл lsкоманды будет не очень понятен для будущих читателей, по крайней мере, без комментариев.)
Jonik

ls -d foo*может возвращаться с ненулевым статусом выхода и по другим причинам, например после a ln -s /nowhere foobar(по крайней мере, в некоторых lsреализациях).
Стефан Шазелас

3

Вы можете сделать, например,

mv 1>/dev/null 2>&1 foo* ~/bar/ или же mv foo* ~/bar/ 1&>2

Для получения более подробной информации смотрите: http://mywiki.wooledge.org/BashFAQ/055


mv foo* ~/bar/ 1&>2не заставляет команду замолчать, она отправляет то, что было бы на stdout, на stderr.
Крис Даун

и если вы используете Mingw под Windows (как я), замените /dev/nullнаNUL
Rian Sanderson

Как работает эта команда? Что делают амперсанды? Что значит 1и 2что?
Аарон Франке

@AaronFranke 1 - это stdout, а 2 - это канал stderr по умолчанию, когда создается процесс Linux. Узнайте больше о трубе в командах Linux. Вы можете начать здесь - digitalocean.com/community/tutorials/…
Mithun B

2

Вы можете обмануть (переносимо) с perl:

perl -e 'system "mv foo* ~/bar/" if glob "foo*"'

Пожалуйста, прокомментируйте перед понижением.
Джозеф Р.

2

Если вы собираетесь использовать Perl, вы можете пройти весь путь:

#!/usr/bin/perl
use strict;
use warnings;
use File::Copy;

my $target = "$ENV{HOME}/bar/";

foreach my $file (<foo*>) {
    move $file, $target  or warn "Error moving $file to $target: $!\n";
}

или как однострочник:

perl -MFile::Copy -E 'move $_, "$ENV{HOME}/bar/" or warn "$_: $!\n" for <foo*>'

(Подробнее о moveкоманде см. Документацию для File :: Copy .)


-1

Вместо

mv foo* ~/bar/

ты можешь сделать

cp foo* ~/bar/
rm foo* 

Просто, читабельно :)


6
Копирование, а затем удаление занимает больше времени, чем простое перемещение. Это также не будет работать, если файл больше доступного свободного места на диске.
Антон

11
ОП хочет решение, которое молчит. Ни, cpни rmмолчать, если foo*не существует.
Рон Ротман

-1
mv foo* ~/bar/ 2>/dev/null

Является ли команда «Выше» успешной или нет, мы можем определить по состоянию выхода предыдущей команды

command: echo $?

если вывод echo $? отличается от 0 означает, что команда неуспешна, если вывод равен 0 означает, что команда успешна

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.