Например, прямо сейчас я использую следующее, чтобы изменить пару файлов, чьи пути Unix я написал в файл:
cat file.txt | while read in; do chmod 755 "$in"; done
Есть ли более элегантный, безопасный способ?
Например, прямо сейчас я использую следующее, чтобы изменить пару файлов, чьи пути Unix я написал в файл:
cat file.txt | while read in; do chmod 755 "$in"; done
Есть ли более элегантный, безопасный способ?
Это потому, что есть не только 1 ответ...
shell
расширение командной строкиxargs
специальный инструментwhile read
с некоторыми замечаниямиwhile read -u
с использованием выделенного fd
для интерактивной обработки (пример)Что касается запроса OP: запуск chmod
для всех целей, перечисленных в файле, xargs
является указанным инструментом. Но для некоторых других приложений небольшое количество файлов и т.д....
Если ваш файл не слишком большой и все файлы имеют правильные имена (без пробелов и других специальных символов, таких как кавычки), вы можете использовать расширение командной строки shell
. Просто:
chmod 755 $(<file.txt)
Для небольшого количества файлов (строк) эта команда является более легкой.
xargs
- правильный инструментДля большего количества файлов или почти для любого количества строк во входном файле...
Для многих инструментов binutils, таких как chown
, chmod
, rm
, cp -t
...
xargs chmod 755 <file.txt
Если у вас есть специальные символы и/или много строк в file.txt
.
xargs -0 chmod 755 < <(tr \\n \\0 <file.txt)
если ваша команда должна быть выполнена ровно 1 раз по записи:
xargs -0 -n 1 chmod 755 < <(tr \\n \\0 <file.txt)
Это не нужно для этого примера, поскольку chmod
принимает несколько файлов в качестве аргумента, но это соответствует названию вопроса.
В некоторых особых случаях вы можете даже определить местоположение аргумента файла в командах, сгенерированных xargs
:
xargs -0 -I '{}' -n 1 myWrapper -arg1 -file='{}' wrapCmd < <(tr \\n \\0 <file.txt)
seq 1 5
в качестве вводаПопробуйте это:
xargs -n 1 -I{} echo Blah {} blabla {}.. < <(seq 1 5)
Blah 1 blabla 1..
Blah 2 blabla 2..
Blah 3 blabla 3..
Blah 4 blabla 4..
Blah 5 blabla 5..
Где командование выполняется один раз в строке.
while read
и варианты.Как предполагает OP, cat file.txt | while read in; do chmod 755 "$in"; done
будет работать, но есть 2 проблемы:
cat |
- бесполезная вилка, и
| while ... ;done
станет недоработкой, и после ;done
среда исчезнет.
Так что это может быть лучше написано:
while read in; do chmod 755 "$in"; done < file.txt
Но,
Вас могут предупредить о флагах $IFS
и read
:
help read
read: read [-r] ... [-d delim] ... [name ...] ... Reads a single line from the standard input... The line is split into fields as with word splitting, and the first word is assigned to the first NAME, the second word to the second NAME, and so on... Only the characters found in $IFS are recognized as word delimiters. ... Options: ... -d delim continue until the first character of DELIM is read, rather than newline ... -r do not allow backslashes to escape any characters ... Exit Status: The return code is zero, unless end-of-file is encountered...
В некоторых случаях вам может понадобиться использовать
while IFS= read -r in;do chmod 755 "$in";done <file.txt
Для избежания проблем с чужими именами файлов. И, может быть, если у вас возникнут проблемы с UTF-8
:
while LANG=C IFS= read -r in ; do chmod 755 "$in";done <file.txt
Пока вы используете STDIN
для чтения file.txt
, ваш сценарий не может быть интерактивным (вы больше не можете использовать STDIN
).
while read -u
, используя выделенный fd
.Синтаксис: while read ...;done <file.txt
перенаправит STDIN
в file.txt
. Это означает, что вы не сможете иметь дело с процессом, пока он не закончится.
Если вы планируете создать интерактивный инструмент, вам следует избегать использования STDIN
и использовать какой-либо альтернативный дескриптор файла.
Дескрипторы файла констант: 0
для STDIN, 1
для STDOUT и 2
для STDERR. Вы можете увидеть их по:
ls -l /dev/fd/
или
ls -l /proc/self/fd/
Оттуда вы должны выбрать неиспользуемый номер между 0
и 63
(на самом деле больше, в зависимости от инструмента суперпользователя sysctl
) в качестве дескриптора файла:
Для этой демонстрации я буду использовать fd 7
:
exec 7<file.txt # Without spaces between '7' and '<'!
ls -l /dev/fd/
Тогда вы можете использовать read -u 7
следующим образом:
while read -u 7 filename;do
ans=;while [ -z "$ans" ];do
read -p "Process file '$filename' (y/n)? " -sn1 foo
[ "$foo" ]&& [ -z "${foo/[yn]}" ]&& ans=$foo || echo '??'
done
if [ "$ans" = "y" ] ;then
echo Yes
echo "Processing '$filename'."
else
echo No
fi
done
Чтобы закрыть fd/7
:
exec 7<&- # This will close file descriptor 7.
ls -l /dev/fd/
Да.
while read in; do chmod 755 "$in"; done < file.txt
Таким образом вы можете избежать процесса cat
.
cat
почти всегда плох для такой цели. Вы можете больше узнать о Бесполезное использование кошки
если у вас есть хороший селектор (например, все .txt файлы в каталоге) вы можете сделать:
for i in *.txt; do chmod 755 "$i"; done
или ваш вариант:
while read line; do chmod 755 "$line"; done <file.txt
Если вы знаете, что на входе нет пробелов:
xargs chmod 755 < file.txt
Если в путях могут быть пробелы, и если у вас есть GNU xargs:
tr '\n' '\0' < file.txt | xargs -0 chmod 755
Если вы хотите запускать свою команду параллельно для каждой строки, вы можете использовать GNU Parallel
parallel -a <your file> <program>
Каждая строка вашего файла будет передана программе в качестве аргумента. По умолчанию parallel
запускает столько потоков, сколько подсчитывает ваш процессор. Но вы можете указать его с помощью -j
Я вижу, что вы отметили bash, но Perl также будет хорошим способом сделать это:
perl -p -e '`chmod 755 $_`' file.txt
Вы также можете применить регулярное выражение, чтобы убедиться, что вы получаете нужные файлы, например. обрабатывать только файлы .txt:
perl -p -e 'if(/\.txt$/) `chmod 755 $_`' file.txt
Чтобы "просмотреть", что происходит, просто замените обратные ссылки на двойные кавычки и добавьте print
:
perl -p -e 'if(/\.txt$/) print "chmod 755 $_"' file.txt
Вы также можете использовать AWK, что даст вам больше возможностей для работы с файлом
awk '{ print "chmod 755 "$0"" | "/bin/sh"}' file.txt
если в вашем файле есть разделитель полей, например:
field1,field2,field3
Чтобы получить только первое поле, которое вы делаете
awk -F, '{ print "chmod 755 "$1"" | "/bin/sh"}' file.txt
Вы можете проверить более подробную информацию о документации GNU.https://www.gnu.org/software/gawk/manual/html_node/Very-Simple.html#Very-Simple
Логика относится ко многим другим целям. А как читать .sh_history каждого пользователя из /home/filesystem? Что, если их будет тысяча?
#!/bin/ksh
last |head -10|awk '{print $1}'|
while IFS= read -r line
do
su - "$line" -c 'tail .sh_history'
done
Вот сценарий https://github.com/imvieira/SysAdmin_DevOps_Scripts/blob/master/get_and_run.sh