Pull to refresh

Comments 63

И тут мы столкнёмся с неоднозначностью поведения cp. Если папки /target не существует, то мы получим то, что нам нужно.

Однако, если папка target существует, то файлы будут скопированы в папку /source/target.

А не /target/source?
Ваше требование оправдано, если речь об орфографии.
Но технические ошибки, влияющие на результат описываемой процедуры или на понимание процесса, должны корректироваться публично.
Это логично для случая, когда вы пропустите поправку в личке, у читающего статью останется шанс дополнить картину чтением комментов.
Тут была очевидная опечатка.

Мне кажется вместо этого лучше использовать опцию -T:
cp -a -T /source /target


-T, --no-target-directory
          treat DEST as a normal file

В некоторых случаях ещё полезен флаг -t, только тогда source и target меняются местами:


SYNOPSIS
       cp [OPTION]... [-T] SOURCE DEST
       cp [OPTION]... SOURCE... DIRECTORY
       cp [OPTION]... -t DIRECTORY SOURCE...
А можете объяснить, чем именно лучше?

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

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

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

$ dd if=/dev/zero of=tmpfile bs=1M count=1
1+0 records in
1+0 records out
1048576 bytes (1.0 MB, 1.0 MiB) copied, 0.00239023 s, 439 MB/s
$ mkfs.fat tmpfile 
mkfs.fat 4.1 (2017-01-24)
$ mmd -i tmpfile test
$ mdir -i tmpfile test
 Volume in drive : has no label
 Volume Serial Number is D0A1-1DD1
Directory for ::/test

.            <DIR>     2019-10-14  14:41 
..           <DIR>     2019-10-14  14:41 
        2 files                   0 bytes
                          1 026 048 bytes free

$ mkdir tmpdir
$ sudo mount -o loop tmpfile tmpdir
$ ls -al tmpdir/test/
total 18
drwxr-xr-x 2 root root  2048 Oct 14  2019 .
drwxr-xr-x 3 root root 16384 Jan  1  1970 ..


Как легко заметить информация про .. — разная для mdir и ls. Почему? Потому что ядро игнорирует . и .., которые могут существовать (а могут и не существовать) на диске. Вместо этого . и .. эмулируются внутри ядра.

Так что в Linux вы никогда не увидите файловых систем без .. В Windows — да, возможно.

В каком смысле "в Windows — да, возможно"? Разве . и .. не точно так же эмулируются?

В Windows 9X — возможно на 100%, там всё как в DOS. Сегодня… Я понятия не имею кто и как это делает в Windows 10 и не может ли какой-нибудь драйвер IFS сделать так, чтобы. и… в каталоге просто не было. Вот реально — не знаю.

В Linux это делается на уровне VFS (и всегда делалось на уровне VFS) и до драйвера дело просто не доходит…

Мне не удалось найти точной информации содержится ли запись .. в директории NTFS, но думаю что вряд ли — это слишком расточительно.


В NTFS, в отличии от других систем, первичным хранилищем информации о файлах являются не записи в директории, а записи в MFT. Содержимое директорий же — лишь B-tree индекс, как в базах данных. И у каждого файла есть по атрибуту $FILE_NAME на каждую директорию, в которой тот находится.


Если бы в директориях были записи .. — это бы означало, что у каждой директории есть столько атрибутов $FILE_NAME, сколько у неё субдиректорий. А поскольку все атрибуты хранятся в плоском массиве — это бы убило всю идею B-tree индексов.


Так что, если только NTFS делали не полные идиоты, физически .. как запись директории там точно не хранится.


А вот через API эта запись ещё как возвращается, так что...

А вот через API эта запись ещё как возвращается, так что...
Совешенно не «так что». Кроме FAT и NTFS есть ведь всякие ISO 9660, UFS и прочие всякие BTRFS. И вот вопрос: всегда ли они эмулируют . и .. — или это от драйвера зависит?

По логике-то должно быть как в Linux: . и .. эмулируются VFS, частью ядра, до драйвера дело не доходит в принципе… но я видел много мест в Windows, где есть подобные layering violations, так что ответить на этот вопрос не могу.
А если в /dev/kmem покопаться — так ещё и не такое можно увидеть.

И даже в вашем случае вы отлично можете сделать что-нибудь типа mkdir ./figvam — и файлик отлично создастся. Более того, даже если вы удалите .. — вы всё равно сможете сделать ls ..

Но да, с коррапнутыми файловыми системами возможны чудеса… вплодо до kernel oops… Неопределённое поведение — оно такое.
Почему не -aT? Минус пробел, минус "-".

Для cp так действительно короче, но бывают программы где склеивание опций приводит к склеиванию ласт неожиданным результатам.

Так вроде стандарт же, однобуквенные опции перечисляются после одного минуса без пробелов, а перед многобуквенными опциями ставят 2 минуса.

Это скорее не стандарт, а обычай, и далеко не все ему следуют (взять хотя бы firefox с его многобуквенными опциями с одним дефисом). И даже по этому стандартному обычаю есть опции, которым требуется аргумент, и не дай божа случайно засунуть другую однобуквенную опцию между многобуквенной и её аргументом (чтобы уточнить поведение, например: cp -aTi /source /target).
Ну и некоторые программы могут вообще не распознать склеенные аргументы, даже если они однобуквенные безаргументные, ибо разработчику интересно прогать, а не аргументы ваши клееные парсить.

И не только firefox.

[pfemidi@pfemidi ~]$ java -version
openjdk version "1.8.0_232"
OpenJDK Runtime Environment (build 1.8.0_232-b09)
OpenJDK 64-Bit Server VM (build 25.232-b09, mixed mode)
[pfemidi@pfemidi ~]$ java --version
Unrecognized option: --version
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
[pfemidi@pfemidi ~]$
Такой вариант тоже добавлен в статью. Это на любителя. Перепутаете регистр T и будет полная ерунда: поменяется направление копирования.

А перепутаете ./source/. и ./source/.. и тоже белиберда получится.


Или ./source /..


Вообще, не надо в командной строке путать что-то.

объясните, пожалуйста, разницу между
> cp /a /b
> cp /a/ /b/
> cp /a/* /b

Как правильно скопировать все значимое содержимое одной папки в другую, при этом находясь в третьей?
Тут нужно использовать ключи -a или хотя бы -r для рекурсии.
Между первой и второй строкой разницы нет.

А вот в третьей мы приплетаем shell, и если в папке нет файлов или есть начинающиеся с точки, то копирование будет произведено не полностью или с ошибкой.
Между первой и второй строкой разницы нет.

В BSD Coreutils (как минимум в FreeBSD и OS X) это работает несколько не так: / после первого аргумента копирует не директорию, а ее содержимое


Пример
$ mkdir -p a/b/c d
$ cp -r a/ d && ls d   
b
$ cp -r a d && ls d
a       b

Первый и второй варианты отличаются в случае, если a — символьная ссылка.


Тогда первая команда скопирует ссылку, а вторая — будет копировать файлы.

Интересный факт, что добавление слэша инициирует переход по ссылке в каталог.
Эх. Я думал вы тут расскажете про тонкости копирования с хард-линками, софт-линками и ситуациями, когда файлы находятся на разных ФС.
А для вашего вопроса есть простой ответ. Нужно использовать cp так:
rsync -a source_dir target_dir
Попутно можно использовать параметр --progress, если хочется видеть проценты выполнения задачи.
Есть десятки способов скопировать файлы под Linux.
В этой статье речь именно о cp.

Вообще-то я хочу в следующих статьях затронуть и ваши вопросы.
Просто это слишком много для одной статьи.
А как запускать dd чтобы был виден прогресс? (оффтоп)

UFO just landed and posted this here
Вот только вы слэш пропустили и всё будет скопировано в target_dir/source_dir

А надо так:
rsync -a source_dir/ target_dir

И то есть риск потерять разные атрибуты или хардлинки. Чтоб совсем ничего не потерять
rsync -aHAX

У меня обычно набор выглядит как rsync -axv

Раз уж его упомянули, будьте добры, поясните про завершающие слэши. Это «фишка» самого rsync-a или общая логика работы с путями в GNU/linux? И чем отличается путь с указанным в конце слешем от пути без него? Не могу осилить пояснение на английском.

Это поведение самого rsync'a, который прямо ему говорит — копируй содержимое директории, а не включай тут вангу с определением "а существует ли в точке назначения такая-то директория, если да то тудааа, если нет то создаёёёём"...


Эдакий source/., упомянутый в начале статьи, только чуть удобнее.

Вместо cp можно использовать tar. Как-то так
tar cf — . | tar xvf — -C /dest

Поэтому я всегда использую rsync c ключиком -a. Ещё добавляю --progress, чтобы смотреть прогресс.
Тогда уж не забывайте про -HAX, а то конфуз может случится.
Тут можно еще напомнить, что в некоторых версиях rsync например нет -AX.
Поддержка -AX появилась в версии 3.0, а это было аж в 2008 году. Шанс встретить версию ниже очень мал.
$> rsync --help | grep archive
-a, --archive archive mode; same as -rlptgoD (no -H)
$> rsync --version
rsync version 2.6.9 protocol version 29
А rsync умеет в --reflink=always? Прогресс — это хорошо, конечно, но зачем же место на диске тратить…

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

Вот простой пример, когда имя файла интерпретируется как опция.
$ mkdir test
$ cd test
$ echo 123 > --help
$ cp * 124
Usage: cp [OPTION]… [-T] SOURCE DEST

Осторожность конечно не помешает, но пробел и прочие мета-символы ничего не порвут (в современных шеллах, по крайней мере), единственная реальная проблема это минус в начале, но и это решается добавлением "--" перед вайлдкардом.

Добавить ./ перед вайдлкардом — надёжнее, чем --
Интересно, полез читать что, как и когда. Вобщем опцию complete_fullquote включили по умолчанию в bash4.2 который зарелизился в 2011 году. www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
Потыкал в разные дистрибутивы через докер и реально везде работает.
Вот пример как тыкал
sudo docker run ubuntu:18.04 bash -l -c 'touch "myfile1 1"; stat myfile*'

Спасибо
bash 4.2 мог зарелизится когда угодно, но в MacOS (даже в macOS Catalina, вышедшей неделю назад) используется bash 3.2.57(1), о чём не стоит забывать…
В комментарии ниже показали, что я ошибся в датировке, когда это заработало. Вероятно, гораздо раньше.
complete_fullquote это вообще не про глоббинг, это только для автодополнения.
Хмм, и правда. Спасибо за дополнение. Тогда я понятия не имею, когда это начало работать. Вероятно, очень давно. А, возможно, у меня в воспоминаниях остались только случаи небезопасного использования переменных типа таких.
$ touch 'a a'

$ for file in *; do stat $file ; done
stat: cannot stat 'a': No such file or directory
stat: cannot stat 'a': No such file or directory

$ for file in *; do stat "$file" ; done
  File: a a
  Size: 0         	Blocks: 16         IO Block: 4096   regular empty file
Device: 35h/53d	Inode: 13125328    Links: 1
Весной 2019 исплевался, экранируя многочисленные пробелы и скрытые символы в именах файлов.
И дело не только в шелле, а в том, что куча софта по разному видит проблему имени файла.
Кубунту и Манджаро.

<зануда>
В файловой системе директории, а не папки.
<\зануда>

Ну какой же вы зануда, если применяете \ там, где должен быть /
Насколько я понимаю ваша команда почти полностью эквивалентна:

cp -ax /source /target


За исключение того, что ваша версия не сохраняет расширенные аттрибуты типа: context, links, xattr, all. А также неоднозначно ведёт себя в зависимости от существования /target.

Для однозначности нужно:

cp -ax /source/. /target
Для большей однозначности можно добавить -T
cp -dpRxT /source /target

Оставаться в пределах одной файловой системы позволяет -x. Мягкие и жёсткие линки копирует. Насчёт расширенных аттрибутов context, links, xattr, all — ими не пользовался.
Sign up to leave a comment.