Чтение пользовательского ввода с обратным чтением

Сначала легкая часть: чтение файлов построчно из оболочки происходит медленно, возможно, вместо этого вы захотите использовать tr. Подробнее см. man tr.

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

tr -- "$1" "$2" <"$file" >"$file".tmp
mv -f -- "$file".tmp "$file"

Очевидная проблема заключается в том, что происходит, если tr терпит неудачу по какой-либо причине (например, потому что $1 пуст). Затем $file.tmp все равно будет создан (он создается, когда строка анализируется оболочкой), затем $file заменяется им.

Таким образом, несколько более безопасный способ сделать это будет выглядеть следующим образом:

tr -- "$1" "$2" <"$file" >"$file".tmp && \
mv -f -- "$file".tmp "$file"

Теперь $file заменяется только тогда, когда tr завершается успешно.

Но что, если есть другой файл с именем $file.tmp? Ну, это будет перезаписано. Здесь все становится сложнее: технически правильный способ сделать это примерно так:

tmp="$( mktemp -t "${0##*/}"_"$$"_.XXXXXXXX )" && \
trap 'rm -f "$tmp"' EXIT HUP INT QUIT TERM || exit 1
tr -- "$1" "$2" <"$file" >"$tmp" && \
cat -- "$tmp" >"$file" && \
rm -f -- "$tmp"

Здесь mktemp создает временный файл; trap гарантирует, что этот файл будет очищен, если скрипт выйдет (нормально или аварийно); и cat -- "$tmp" >"$file" используется вместо mv, чтобы убедиться, что разрешения $file сохранены. Это все равно может завершиться ошибкой, если, скажем, у вас нет прав на запись в $file, но временный файл в конечном итоге будет удален.

Кроме того, GNU sed имеет возможность редактировать файлы на месте, поэтому вы можете сделать что-то вроде этого:

sed -i "s/$1/$2/g" "$file"

Однако это тоже не так просто, как кажется. Вышеприведенное не удастся, если $1 или $2 имеют особое значение для sed. Поэтому вам нужно сначала сбежать от них:

in=$(  printf '%s\n' "$1" | sed 's:[][\/.^$*]:\\&:g' )
out=$( printf '%s\n' "$2" | sed 's:[\/&]:\\&:g;$!s/$/\\/' )
sed -i "s/$in/$out/" "$file"
0
27.04.2016, 17:02
0 ответов

Теги

Похожие вопросы