Как удалить конечные пробелы с помощью sed?

113

У меня есть простой сценарий оболочки, который удаляет конечные пробелы из файла. Есть ли способ сделать этот скрипт более компактным (без создания временного файла)?

sed 's/[ \t]*$//' $1 > $1__.tmp
cat $1__.tmp > $1
rm $1__.tmp
Виктор
источник
2
Вы можете использовать mvвместо catи rm. Почему вы catвообще так используете? Почему бы не использовать cp?
Приостановлено до дальнейшего уведомления.
1
Я использовал знания, полученные из этого вопроса, для создания сценария оболочки для рекурсивного удаления конечных пробелов .
Дэвид Туйт
1
Ваше решение действительно лучше при использовании MinGW из-за ошибки в sed в Windows: stackoverflow.com/questions/14313318/…
Коди Пирсолл
Обратите внимание, что использование catдля перезаписи исходного файла вместо mvфактической замены данных в исходном файле (т. Е. Не приведет к разрыву жестких ссылок). Использование того, sed -iчто предлагается во многих решениях, не поможет. Айо, просто продолжай делать то, что делаешь.
Уильям Перселл

Ответы:

157

Вы можете использовать в месте опции -iиз sedдля Linux и Unix:

sed -i 's/[ \t]*$//' "$1"

Имейте в виду, что выражение удалит завершающие tсимволы в OSX (вы можете использовать, gsedчтобы избежать этой проблемы). Он также может удалить их на BSD.

Если у вас нет gsed, вот правильный (но трудный для чтения) синтаксис sed для OSX:

sed -i '' -E 's/[ '$'\t'']+$//' "$1"

Три строки в одинарных кавычках в конечном итоге объединяются в один аргумент / выражение. В bash нет оператора конкатенации, вы просто помещаете строки одну за другой без пробелов между ними.

В $'\t'решает , как буквальная вкладку-символ в Баше ( с использованием ANSI-C со ссылкой ), так что язычок правильно сцеплен в выражение.

codaddict
источник
1
На моем компьютере появляется следующее, которое я не могу обновить: sed: Not a recognized flag: i
javaPlease42,
2
хм. он также глючен в том смысле, что удаляет все завершающие "t" :)
Good Person
2
«sed: не распознанный флаг: i -» Это происходит в OSX. Вам нужно добавить расширение для файла резервной копии после -i на Mac. например: sed -i .bak 's / [\ t] * $ //' $ 1
Эймон Бустардо
1
@GoodPerson Если вы не шутите, вы, скорее всего, забудете убежать от t:) \t- вкладка для тех, кто еще не знает.
Шон Оллред
2
@SeanAllred не шутил: он полностью сломан, если только вы не используете GNU sed (который сломан и многими другими способами)
Good Person
59

По крайней мере, в Mountain Lion ответ Виктора также удалит символ «t», когда он находится в конце строки. Следующие исправления этой проблемы:

sed -i '' -e's/[[:space:]]*$//' "$1"
акроллет
источник
1
Мой sed также хотел -Eуказать «расширенные (современные) регулярные выражения»
Джаред Бек
Прекрасно работает на OS X. Большое спасибо.
jww
1
Ответ codaddict имеет ту же проблему в OS X (теперь macOS). Это единственное решение на этой платформе.
Франклин Ю
@JaredBeck Шахта sedна Эль-Капитане - нет.
Франклин Ю
19

Спасибо codaddict за предложенный -iвариант.

Следующая команда решает проблему на Snow Leopard.

sed -i '' -e's/[ \t]*$//' "$1"
Виктор
источник
Я нашел это здесь, joemaller.com/823/quick-note-about-seds-edit-in-place-option
Виктор
7
Как говорит @acrollet, вы не можете использовать \tsed, кроме GNU sed, и он интерпретируется как буквальная буква t. Команда только кажется работающей, вероятно, потому, что в завершающем пробеле нет табуляции или tв конце предложения в вашем файле. Использование ''без указания суффикса резервной копии не рекомендуется.
Scrutinizer
13

Лучше всего также указать 1 доллар:

sed -i.bak 's/[[:blank:]]*$//' "$1"
Scrutinizer
источник
5
var1="\t\t Test String trimming   "
echo $var1
Var2=$(echo "${var1}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
echo $Var2
Сандип Патель - SM
источник
1
Эй, это как раз то, что мне нужно! В других опубликованных решениях sed возникла проблема с интеграцией с назначением переменных по конвейеру (а также по конвейеру и по конвейеру ...) в моем сценарии bash, но ваше сработало из коробки.
Эрик Л.
4

У меня есть сценарий в моем .bashrc, который работает под OSX и Linux (только bash!)

function trim_trailing_space() {
  if [[ $# -eq 0 ]]; then
    echo "$FUNCNAME will trim (in place) trailing spaces in the given file (remove unwanted spaces at end of lines)"
    echo "Usage :"
    echo "$FUNCNAME file"
    return
  fi
  local file=$1
  unamestr=$(uname)
  if [[ $unamestr == 'Darwin' ]]; then
    #specific case for Mac OSX
    sed -E -i ''  's/[[:space:]]*$//' $file
  else
    sed -i  's/[[:space:]]*$//' $file
  fi
}

к которому я добавляю:

SRC_FILES_EXTENSIONS="js|ts|cpp|c|h|hpp|php|py|sh|cs|sql|json|ini|xml|conf"

function find_source_files() {
  if [[ $# -eq 0 ]]; then
    echo "$FUNCNAME will list sources files (having extensions $SRC_FILES_EXTENSIONS)"
    echo "Usage :"
    echo "$FUNCNAME folder"
    return
  fi
  local folder=$1

  unamestr=$(uname)
  if [[ $unamestr == 'Darwin' ]]; then
    #specific case for Mac OSX
    find -E $folder -iregex '.*\.('$SRC_FILES_EXTENSIONS')'
  else
    #Rhahhh, lovely
    local extensions_escaped=$(echo $SRC_FILES_EXTENSIONS | sed s/\|/\\\\\|/g)
    #echo "extensions_escaped:$extensions_escaped"
    find $folder -iregex '.*\.\('$extensions_escaped'\)$'
  fi
}

function trim_trailing_space_all_source_files() {
  for f in $(find_source_files .); do trim_trailing_space $f;done
}
Паскаль Т.
источник
3

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

С GNU sed:

sed -Ei 's/[ \t]+$//' "$1"
sed -i 's/[ \t]\+$//' "$1"   # The same without extended regex

Я также быстро протестировал кое-что еще: использование [ \t]вместо [[:space:]]также значительно ускоряет процесс (GNU sed v4.4):

sed -Ei 's/[ \t]+$//' "$1"

real    0m0,335s
user    0m0,133s
sys 0m0,193s

sed -Ei 's/[[:space:]]+$//' "$1"

real    0m0,838s
user    0m0,630s
sys 0m0,207s

sed -Ei 's/[ \t]*$//' "$1"

real    0m0,882s
user    0m0,657s
sys 0m0,227s

sed -Ei 's/[[:space:]]*$//' "$1"

real    0m1,711s
user    0m1,423s
sys 0m0,283s
Yolenoyer
источник
1

Просто для развлечения:

#!/bin/bash

FILE=$1

if [[ -z $FILE ]]; then
   echo "You must pass a filename -- exiting" >&2
   exit 1
fi

if [[ ! -f $FILE ]]; then
   echo "There is not file '$FILE' here -- exiting" >&2
   exit 1
fi

BEFORE=`wc -c "$FILE" | cut --delimiter=' ' --fields=1`

# >>>>>>>>>>
sed -i.bak -e's/[ \t]*$//' "$FILE"
# <<<<<<<<<<

AFTER=`wc -c "$FILE" | cut --delimiter=' ' --fields=1`

if [[ $? != 0 ]]; then
   echo "Some error occurred" >&2
else
   echo "Filtered '$FILE' from $BEFORE characters to $AFTER characters"
fi
Дэвид Тонхофер
источник
0

В конкретном случае sed, то -iвариант , что другие уже упоминалось далеко и далеко самый простой и здравомыслящий один.

В более общем случае, spongeиз moreutilsколлекции делает именно то, что вы хотите: он позволяет вам заменить файл результатом его обработки способом, специально разработанным для того, чтобы этап обработки не споткнулся о себе, перезаписывая тот самый файл, в котором он находится. работа над. Чтобы процитировать spongeстраницу руководства :

sponge читает стандартный ввод и записывает его в указанный файл. В отличие от перенаправления оболочки, sponge впитывает весь ввод перед записью файла вывода. Это позволяет создавать конвейеры, которые читают и записывают в один и тот же файл.

https://joeyh.name/code/moreutils/

Дэн Мартинес
источник
-1

Чтобы удалить только пробелы (в моем случае пробелы и табуляции) из строк, содержащих хотя бы один непробельный символ (таким образом, пустые строки с отступом не затрагиваются):

sed -i -r 's/([^ \t]+)[ \t]+$/\1/' "$file"
PHK
источник