Почему эта команда для копирования файлов в цикле for работает в bash, но не в zsh?

Я пытаюсь скопировать кучу файлов с тем же именем, но в разных подкаталогах, в один каталог, меняя имена на имена на основе путей к исходным файлам. Я использую цикл for, который делает то, что я намереваюсь в bash, но ведет себя очень странно в zsh.

Команда (добавлены строки для разборчивости):

for f in */Flavors/*/stuff1/filename.txt; do l=$(echo $f | cut -d'/' --output-delimiter '' -f1,3); dest=/stuff2/${l}.utf8.txt; echo $f $dest; cp -v -- $f $dest; done 

Выход в zsh. Я предполагаю, что имена выходных файлов будут, например, «EnglishAU.utf8.txt», но вместо этого они просто «английский», «французский» и «испанский». Обратите внимание, что echo в цикле показывает $dest содержащий правильный путь, а затем cp использует неверный!

 English/Flavors/AU/stuff1/filename.txt /stuff2/EnglishAU.utf8.txt `English/Flavors/AU/stuff1/filename.txt' -> `/stuff2/English' English/Flavors/UK/stuff1/filename.txt /stuff2/EnglishUK.utf8.txt `English/Flavors/UK/stuff1/filename.txt' -> `/stuff2/English' English/Flavors/US/stuff1/filename.txt /stuff2/EnglishUS.utf8.txt `English/Flavors/US/stuff1/filename.txt' -> `/stuff2/English' French/Flavors/CA/stuff1/filename.txt /stuff2/FrenchCA.utf8.txt `French/Flavors/CA/stuff1/filename.txt' -> `/stuff2/French' French/Flavors/FR/stuff1/filename.txt /stuff2/FrenchFR.utf8.txt `French/Flavors/FR/stuff1/filename.txt' -> `/stuff2/French' Spanish/Flavors/ES/stuff1/filename.txt /stuff2/SpanishES.utf8.txt `Spanish/Flavors/ES/stuff1/filename.txt' -> `/stuff2/Spanish' Spanish/Flavors/OT/stuff1/filename.txt /stuff2/SpanishOT.utf8.txt `Spanish/Flavors/OT/stuff1/filename.txt' -> `/stuff2/Spanish' 

Как упоминалось выше, это работает так, как предполагалось в bash. Что делает zsh?

Это происходит потому, что cut выводит NULL-символы на выходе. Вы не можете передавать аргументы программы, содержащие нулевой символ (см. Это ).

В bash это работает, потому что bash не может обрабатывать NULL-символы в строках, и он удаляет их. Zsh немного более мощный, и он может обрабатывать символы NULL. Однако, когда приходит время передать строку в программу, она все еще содержит нуль, который сигнализирует о конце аргумента.

Давайте рассмотрим это подробно.

 $ echo 'English/Flavors/AU/stuff1/filename.txt' | cut -d'/' --output-delimiter '' -f1,3 | xxd 0000000: 456e 676c 6973 6800 4155 0a English.AU. 

Здесь мы моделировали один из ваших файлов, пропуская путь через cut . Обратите внимание на вывод xxd который имеет NULL-символ между English и AU .

Теперь давайте прогоним и смоделируем остальную часть скрипта.

 $ l=$(echo 'English/Flavors/AU/stuff1/filename.txt' | cut -d'/' --output-delimiter '' -f1,3) $ dest=/stuff2/${l}.utf8.txt $ echo "$dest" | xxd 0000000: 2f73 7475 6666 322f 456e 676c 6973 6800 /stuff2/English. 0000010: 4155 2e75 7466 382e 7478 740a AU.utf8.txt. 

Обратите внимание на NULL после English . echo отображает его правильно, потому что echo является встроенной оболочкой. Если мы используем внешнее echo , оно также проявляет проблему.

 $ /bin/echo "$dest" | xxd 0000000: 2f73 7475 6666 322f 456e 676c 6973 680a /stuff2/English. 

PS Вы действительно должны цитировать тоже 🙂


Решение состоит в том, чтобы не использовать cut , вместо этого используйте awk .

 $ echo 'English/Flavors/AU/stuff1/filename.txt' | awk -F/ '{ print $1$3 }' | xxd 0000000: 456e 676c 6973 6841 550a EnglishAU.