Bagaimana cara memotong spasi dari variabel Bash?

922

Saya memiliki skrip shell dengan kode ini:

var=`hg st -R "$path"`
if [ -n "$var" ]; then
    echo $var
fi

Tetapi kode kondisional selalu dijalankan, karena hg stselalu mencetak setidaknya satu karakter baris baru.

  • Apakah ada cara sederhana untuk menghapus spasi $var(seperti trim()dalam PHP )?

atau

  • Apakah ada cara standar untuk menangani masalah ini?

Saya bisa menggunakan sed atau AWK , tetapi saya ingin berpikir ada solusi yang lebih elegan untuk masalah ini.

terlalu banyak php
sumber
3
Terkait, jika Anda ingin memangkas ruang pada bilangan bulat dan hanya mendapatkan bilangan bulat, bungkus dengan $ (($ var)), dan bahkan dapat melakukannya ketika di dalam tanda kutip ganda. Ini menjadi penting ketika saya menggunakan pernyataan tanggal dan dengan nama file.
Volomike
"Apakah ada cara standar untuk menangani masalah ini?" Ya, gunakan [[bukan [. $ var=$(echo) $ [ -n $var ]; echo $? #undesired test return 0 $ [[ -n $var ]]; echo $? 1
user.friendly
Jika itu membantu, setidaknya di mana saya mengujinya di Ubuntu 16.04. Menggunakan pertandingan berikut memangkas dalam segala hal: echo " This is a string of char " | xargs. Jika Anda namun memiliki satu kutipan dalam teks Anda dapat melakukan hal berikut: echo " This i's a string of char " | xargs -0. Perhatikan bahwa saya menyebutkan xarg terbaru (4.6.0)
Luis Alvarado
Kondisi ini tidak benar karena baris baru sebagai backticks menelan baris baru terakhir. Ini tidak akan mencetak apa pun test=`echo`; if [ -n "$test" ]; then echo "Not empty"; fi, ini akan test=`echo "a"`; if [ -n "$test" ]; then echo "Not empty"; fi- jadi harus ada lebih dari sekadar baris baru di akhir.
Mecki
A = "123 4 5 6"; B = echo $A | sed -r 's/( )+//g';
bruziuz

Jawaban:

1022

Mari kita definisikan variabel yang berisi spasi spasi awal, jejak, dan menengah:

FOO=' test test test '
echo -e "FOO='${FOO}'"
# > FOO=' test test test '
echo -e "length(FOO)==${#FOO}"
# > length(FOO)==16

Cara menghapus semua spasi putih (dilambangkan dengan [:space:]dalam tr):

FOO=' test test test '
FOO_NO_WHITESPACE="$(echo -e "${FOO}" | tr -d '[:space:]')"
echo -e "FOO_NO_WHITESPACE='${FOO_NO_WHITESPACE}'"
# > FOO_NO_WHITESPACE='testtesttest'
echo -e "length(FOO_NO_WHITESPACE)==${#FOO_NO_WHITESPACE}"
# > length(FOO_NO_WHITESPACE)==12

Cara menghapus spasi putih saja:

FOO=' test test test '
FOO_NO_LEAD_SPACE="$(echo -e "${FOO}" | sed -e 's/^[[:space:]]*//')"
echo -e "FOO_NO_LEAD_SPACE='${FOO_NO_LEAD_SPACE}'"
# > FOO_NO_LEAD_SPACE='test test test '
echo -e "length(FOO_NO_LEAD_SPACE)==${#FOO_NO_LEAD_SPACE}"
# > length(FOO_NO_LEAD_SPACE)==15

Cara menghapus trailing space saja:

FOO=' test test test '
FOO_NO_TRAIL_SPACE="$(echo -e "${FOO}" | sed -e 's/[[:space:]]*$//')"
echo -e "FOO_NO_TRAIL_SPACE='${FOO_NO_TRAIL_SPACE}'"
# > FOO_NO_TRAIL_SPACE=' test test test'
echo -e "length(FOO_NO_TRAIL_SPACE)==${#FOO_NO_TRAIL_SPACE}"
# > length(FOO_NO_TRAIL_SPACE)==15

Cara menghapus spasi depan dan akhir - rangkai sed:

FOO=' test test test '
FOO_NO_EXTERNAL_SPACE="$(echo -e "${FOO}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
echo -e "FOO_NO_EXTERNAL_SPACE='${FOO_NO_EXTERNAL_SPACE}'"
# > FOO_NO_EXTERNAL_SPACE='test test test'
echo -e "length(FOO_NO_EXTERNAL_SPACE)==${#FOO_NO_EXTERNAL_SPACE}"
# > length(FOO_NO_EXTERNAL_SPACE)==14

Atau, jika bash Anda mendukungnya, Anda dapat menggantinya echo -e "${FOO}" | sed ...dengan sed ... <<<${FOO}, seperti: (untuk mengekstrak spasi putih):

FOO_NO_TRAIL_SPACE="$(sed -e 's/[[:space:]]*$//' <<<${FOO})"
MattyV
sumber
63
Untuk menggeneralisasi solusi untuk menangani semua bentuk spasi, ganti karakter spasi di trdan sedperintah dengan [[:space:]]. Perhatikan bahwa sedpendekatan ini hanya akan bekerja pada input satu baris . Untuk pendekatan yang bekerja dengan input multi-line dan juga menggunakan fitur built-in bash, lihat jawabannya oleh @bashfu dan @GuruM. Versi umum, inline dari solusi @Nicholas Sushkin akan terlihat seperti ini: trimmed=$([[ " test test test " =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]; echo -n "${BASH_REMATCH[1]}")
mklement0
7
Jika Anda sering melakukannya, menambahkan alias trim="sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*\$//g'"ke Anda ~/.profilememungkinkan Anda untuk menggunakan echo $SOMEVAR | trimdan cat somefile | trim.
instanceof saya
Aku menulis sebuah sedsolusi yang hanya menggunakan ekspresi tunggal daripada dua: sed -r 's/^\s*(\S+(\s+\S+)*)\s*$/\1/'. Ini memotong whitespace memimpin dan mengikuti, dan menangkap urutan karakter wh-spasi yang dipisahkan whitespace di tengah. Nikmati!
Victor Zamanian
@VictorZamanian Solusi Anda tidak berfungsi jika input hanya berisi spasi putih. Solusi dua pola sed yang diberikan oleh MattyV dan instanceof saya bekerja dengan baik dengan input spasi putih saja.
Torben
@Torben Fair point. Saya kira ungkapan tunggal dapat dibuat bersyarat, dengan |, sehingga tetap sebagai satu ekspresi tunggal, bukan beberapa.
Victor Zamanian
966

Jawaban sederhana adalah:

echo "   lol  " | xargs

Xargs akan melakukan pemotongan untuk Anda. Ini satu perintah / program, tanpa parameter, mengembalikan string yang dipangkas, semudah itu!

Catatan: ini tidak menghapus semua ruang internal jadi "foo bar"tetap sama; TIDAK menjadi "foobar". Namun, beberapa ruang akan diringkas menjadi satu ruang, jadi "foo bar"akan menjadi "foo bar". Selain itu tidak menghapus karakter garis akhir.

berlaku
sumber
27
Bagus. Ini bekerja dengan sangat baik. Saya telah memutuskan untuk mem-pipe-nya xargs echohanya untuk menjadi verbose tentang apa yang saya lakukan, tetapi xargs sendiri akan menggunakan gema secara default.
Will
24
Trik yang bagus, tetapi berhati-hatilah, Anda dapat menggunakannya untuk string satu baris tetapi "oleh desain xargs" itu tidak akan hanya melakukan triming dengan konten pipa multi-line. sed adalah temanmu kalau begitu.
Jocelyn delalande
22
Satu-satunya masalah dengan xargs adalah bahwa ia akan memperkenalkan baris baru, jika Anda ingin menjaga baris baru saya akan merekomendasikan sed 's/ *$//'sebagai alternatif. Anda dapat melihat xargsbaris seperti ini: echo -n "hey thiss " | xargs | hexdump Anda akan melihat 0a73yang amerupakan baris baru. Jika Anda melakukan hal yang sama dengan sed: echo -n "hey thiss " | sed 's/ *$//' | hexdumpAnda akan melihat 0073, tidak ada baris baru.
8
Cermat; ini akan pecah jika string ke xargs berisi ruang kelebihan di antaranya. Seperti "ini satu argumen". xargs akan dibagi menjadi empat.
Bos
64
Ini buruk. 1. Ini akan berubah a<space><space>bmenjadi a<space>b. 2. Bahkan lebih: itu akan berubah a"b"c'd'emenjadi abcde. 3. Bahkan lebih: itu akan gagal a"b, dll.
Sasha
359

Ada solusi yang hanya menggunakan Bash built-in yang disebut wildcard :

var="    abc    "
# remove leading whitespace characters
var="${var#"${var%%[![:space:]]*}"}"
# remove trailing whitespace characters
var="${var%"${var##*[![:space:]]}"}"   
printf '%s' "===$var==="

Inilah yang sama terbungkus dalam suatu fungsi:

trim() {
    local var="$*"
    # remove leading whitespace characters
    var="${var#"${var%%[![:space:]]*}"}"
    # remove trailing whitespace characters
    var="${var%"${var##*[![:space:]]}"}"   
    printf '%s' "$var"
}

Anda melewatkan string yang akan dipotong dalam bentuk yang dikutip. misalnya:

trim "   abc   "

Satu hal yang menyenangkan tentang solusi ini adalah ia akan bekerja dengan semua shell yang sesuai dengan POSIX.

Referensi

Mateusz Piotrowski
sumber
18
Pintar! Ini adalah solusi favorit saya karena menggunakan fungsionalitas bash bawaan. Terima kasih untuk posting! @San, itu dua trim string bersarang. Misalnya, s=" 1 2 3 "; echo \""${s%1 2 3 }"\"akan memangkas segalanya dari akhir, mengembalikan yang terkemuka " ". Subbing 1 2 3 dengan [![:space:]]*mengatakannya untuk "menemukan karakter non-spasi pertama, kemudian clobber dan semuanya setelah". Menggunakan %%bukannya %membuat operasi trim-dari-akhir serakah. Ini bersarang di trim-dari-awal non-serakah, sehingga efeknya, Anda trim " "dari awal. Kemudian, tukar%, #, dan * untuk ruang akhir. Bam!
Mark G.
2
Saya belum menemukan efek samping yang tidak diinginkan, dan kode utama berfungsi dengan kerang mirip POSIX lainnya. Namun, di bawah Solaris 10, itu tidak bekerja dengan /bin/sh(hanya dengan /usr/xpg4/bin/sh, tetapi ini bukan apa yang akan digunakan dengan skrip sh biasa).
vinc17
9
Solusi yang jauh lebih baik daripada menggunakan sed, tr dll, karena jauh lebih cepat, menghindari garpu (). Pada Cygwin perbedaan dalam kecepatan adalah urutan besarnya.
Gene Pavlovsky
9
@San Awalnya saya bingung karena saya pikir ini adalah ekspresi reguler. Mereka tidak. Sebaliknya, ini adalah sintaks Pencocokan Pola ( gnu.org/software/bash/manual/html_node/Pattern-Matching.html , wiki.bash-hackers.org/syntax/pattern ) yang digunakan dalam Penghapusan Substring ( tldp.org/LDP/abs /html/string-manipulation.html ). Jadi ${var%%[![:space:]]*}kata "hapus dari varsubstring terpanjang yang dimulai dengan karakter non-spasi". Itu berarti Anda hanya dibiarkan dengan spasi terkemuka, yang selanjutnya Anda hapus ${var#... Baris berikut (trailing) adalah kebalikannya.
Ohad Schneider
8
Ini adalah solusi yang sangat ideal. Forking satu atau lebih eksternal proses (misalnya, awk, sed, tr, xargs) hanya untuk spasi langsing dari satu string pada dasarnya gila - terutama ketika sebagian besar kerang (termasuk bash) sudah memberikan string yang asli fasilitas munging out-of-the-box.
Cecil Curry
81

Bash memiliki fitur yang disebut ekspansi parameter , yang, antara lain, memungkinkan penggantian string berdasarkan apa yang disebut pola (pola menyerupai ekspresi reguler, tetapi ada perbedaan dan batasan mendasar). [Baris asli flussence: Bash memiliki ekspresi reguler, tetapi mereka tersembunyi dengan baik:]

Berikut ini menunjukkan cara menghapus semua ruang putih (bahkan dari interior) dari nilai variabel.

$ var='abc def'
$ echo "$var"
abc def
# Note: flussence's original expression was "${var/ /}", which only replaced the *first* space char., wherever it appeared.
$ echo -n "${var//[[:space:]]/}"
abcdef
user42092
sumber
2
Atau lebih tepatnya, itu berfungsi untuk ruang di tengah var, tetapi tidak ketika saya mencoba untuk jangkar di akhir.
Paul Tomblin
Apakah ini membantu? Dari manual: "$ {parameter / pattern / string} [...] Jika pola dimulai dengan%, itu harus cocok di akhir nilai parameter yang diperluas."
@ Tidak, jadi itu bukan ekspresi yang benar-benar biasa, tetapi sesuatu yang serupa?
Paul Tomblin
3
Mereka regex, hanya dialek yang aneh.
13
${var/ /}menghapus karakter spasi pertama. ${var// /}menghapus semua karakter spasi. Tidak ada cara untuk memotong hanya memimpin dan mengikuti spasi dengan hanya konstruksi ini.
Gilles 'SO- berhenti bersikap jahat'
60

Untuk menghapus semua spasi dari awal dan akhir string (termasuk karakter akhir baris):

echo $variable | xargs echo -n

Ini juga akan menghapus ruang duplikat:

echo "  this string has a lot       of spaces " | xargs echo -n

Menghasilkan: 'string ini memiliki banyak ruang'

rkachach
sumber
5
Pada dasarnya xargs menghapus semua pembatas dari string. Secara default menggunakan spasi sebagai pembatas (ini bisa diubah dengan opsi -d).
rkachach
4
Sejauh ini, ini adalah solusi paling bersih (pendek dan mudah dibaca).
Potherca
Mengapa Anda membutuhkannya echo -n? echo " my string " | xargsmemiliki output yang sama.
bfontaine
echo -n menghapus akhir baris juga
rkachach
55

Strip satu memimpin dan satu spasi tambahan

trim()
{
    local trimmed="$1"

    # Strip leading space.
    trimmed="${trimmed## }"
    # Strip trailing space.
    trimmed="${trimmed%% }"

    echo "$trimmed"
}

Sebagai contoh:

test1="$(trim " one leading")"
test2="$(trim "one trailing ")"
test3="$(trim " one leading and one trailing ")"
echo "'$test1', '$test2', '$test3'"

Keluaran:

'one leading', 'one trailing', 'one leading and one trailing'

Lepaskan semua ruang depan dan belakang

trim()
{
    local trimmed="$1"

    # Strip leading spaces.
    while [[ $trimmed == ' '* ]]; do
       trimmed="${trimmed## }"
    done
    # Strip trailing spaces.
    while [[ $trimmed == *' ' ]]; do
        trimmed="${trimmed%% }"
    done

    echo "$trimmed"
}

Sebagai contoh:

test4="$(trim "  two leading")"
test5="$(trim "two trailing  ")"
test6="$(trim "  two leading and two trailing  ")"
echo "'$test4', '$test5', '$test6'"

Keluaran:

'two leading', 'two trailing', 'two leading and two trailing'
wjandrea
sumber
9
Ini akan memangkas hanya 1 karakter spasi. Jadi gema menghasilkan'hello world ', 'foo bar', 'both sides '
Joe
@ Jo, saya menambahkan opsi yang lebih baik.
wjandrea
42

Dari bagian Bash Guide tentang globbing

Untuk menggunakan extglob dalam ekspansi parameter

 #Turn on extended globbing  
shopt -s extglob  
 #Trim leading and trailing whitespace from a variable  
x=${x##+([[:space:]])}; x=${x%%+([[:space:]])}  
 #Turn off extended globbing  
shopt -u extglob  

Berikut fungsi yang sama yang dibungkus dalam suatu fungsi (CATATAN: Perlu mengutip string input yang diteruskan ke fungsi):

trim() {
    # Determine if 'extglob' is currently on.
    local extglobWasOff=1
    shopt extglob >/dev/null && extglobWasOff=0 
    (( extglobWasOff )) && shopt -s extglob # Turn 'extglob' on, if currently turned off.
    # Trim leading and trailing whitespace
    local var=$1
    var=${var##+([[:space:]])}
    var=${var%%+([[:space:]])}
    (( extglobWasOff )) && shopt -u extglob # If 'extglob' was off before, turn it back off.
    echo -n "$var"  # Output trimmed string.
}

Pemakaian:

string="   abc def ghi  ";
#need to quote input-string to preserve internal white-space if any
trimmed=$(trim "$string");  
echo "$trimmed";

Jika kita mengubah fungsi untuk dieksekusi dalam subkulit, kita tidak perlu khawatir tentang memeriksa opsi shell saat ini untuk extglob, kita bisa mengaturnya tanpa mempengaruhi shell saat ini. Ini sangat menyederhanakan fungsi. Saya juga memperbarui parameter posisi "di tempat" jadi saya bahkan tidak perlu variabel lokal

trim() {
    shopt -s extglob
    set -- "${1##+([[:space:]])}"
    printf "%s" "${1%%+([[:space:]])}" 
}

begitu:

$ s=$'\t\n \r\tfoo  '
$ shopt -u extglob
$ shopt extglob
extglob         off
$ printf ">%q<\n" "$s" "$(trim "$s")"
>$'\t\n \r\tfoo  '<
>foo<
$ shopt extglob
extglob         off
GuruM
sumber
2
seperti yang telah Anda amati trim () hanya menghapus spasi spasi awalan dan jejak.
GuruM
Seperti mkelement telah mencatat Anda harus melewati parameter fungsi sebagai string yang dikutip yaitu $ (trim "$ string") alih-alih $ (trim $ string). Saya telah memperbarui kode untuk menunjukkan penggunaan yang benar. Terima kasih.
GuruM
Sejauh saya menghargai mengetahui tentang opsi shell, saya tidak berpikir hasil akhirnya lebih elegan daripada hanya melakukan 2 pergantian pola
sehe
Perhatikan bahwa (dengan versi Bash yang cukup baru?), Anda dapat menyederhanakan mekanisme untuk mengembalikan opsi extglob, dengan menggunakan shopt -p: cukup tulis local restore="$(shopt -p extglob)" ; shopt -s extglobdi awal fungsi Anda, dan eval "$restore"di akhir (kecuali, memang, eval itu jahat ...).
Maëlan
Solusi hebat! Satu peningkatan potensial: sepertinya [[:space:]]bisa diganti dengan, well, spasi: ${var##+( )}dan ${var%%+( )}berfungsi dengan baik dan lebih mudah dibaca.
DKroot
40

Anda dapat memotongnya hanya dengan echo:

foo=" qsdqsd qsdqs q qs   "

# Not trimmed
echo \'$foo\'

# Trim
foo=`echo $foo`

# Trimmed
echo \'$foo\'
VAmp
sumber
Ini runtuh beberapa ruang yang berdekatan menjadi satu.
Evgeni Sergeev
7
Apakah Anda mencobanya saat fooberisi wildcard? misalnya, foo=" I * have a wild card"... kejutan! Selain itu, ini runtuh beberapa ruang yang berdekatan menjadi satu.
gniourf_gniourf
5
Ini adalah solusi yang sangat baik jika Anda: 1. ingin tidak ada spasi di ujung 2. hanya ingin satu ruang antara setiap kata 3. bekerja dengan input yang terkontrol tanpa wildcard. Ini pada dasarnya mengubah daftar yang diformat buruk menjadi yang baik.
musicin3d
Pengingat yang bagus dari wildcard @gniourf_gniourf +1. Masih merupakan solusi excelente, Vamp. +1 untuk Anda juga.
Dr Beco
25

Saya selalu melakukannya dengan sed

  var=`hg st -R "$path" | sed -e 's/  *$//'`

Jika ada solusi yang lebih elegan, saya harap seseorang mempostingnya.

Paul Tomblin
sumber
bisakah Anda menjelaskan sintaksisnya sed?
farid99
2
Ekspresi reguler cocok dengan semua spasi spasi tambahan dan menggantinya dengan apa pun.
Paul Tomblin
4
Bagaimana dengan memimpin ruang putih?
Qian Chen
Ini menghapus semua spasi spasi sed -e 's/\s*$//'. Penjelasan: 's' berarti pencarian, '\ s' berarti semua spasi putih, '*' berarti nol atau banyak, '$' berarti sampai akhir baris dan '//' berarti mengganti semua kecocokan dengan string kosong .
Craig
Di 's / * $ //', mengapa ada 2 spasi sebelum tanda bintang, bukannya satu spasi? Apakah itu salah cetak?
Brent212
24

Anda dapat menghapus baris baru dengan tr:

var=`hg st -R "$path" | tr -d '\n'`
if [ -n $var ]; then
    echo $var
done
Adam Rosenfield
sumber
8
Saya tidak ingin menghapus '\ n' dari tengah-tengah string, hanya dari awal atau akhir.
terlalu banyak php
24

Dengan fitur pencocokan pola diperpanjang Bash diaktifkan ( shopt -s extglob), Anda dapat menggunakan ini:

{trimmed##*( )}

untuk menghapus jumlah ruang utama yang sewenang-wenang.

Mooshu
sumber
Hebat! Saya pikir ini adalah solusi yang paling ringan dan elegan.
dubiousjim
1
Lihat @ posting GuruM di bawah ini untuk serupa, tetapi solusi yang lebih generik yang (a) penawaran dengan semua bentuk ruang putih dan (b) juga menangani membuntuti ruang putih.
mklement0
@melemen +1 untuk mengambil kesulitan menulis ulang cuplikan kode saya sebagai fungsi. Terima kasih
GuruM
Bekerja dengan default / bin / ksh OpenBSD juga. /bin/sh -o posixbekerja juga, tetapi saya curiga.
Clint Pachl
Bukan bash wizard di sini; apa trimmed? Apakah itu hal bawaan atau variabel yang sedang dipangkas?
Abhijit Sarkar
19
# Trim whitespace from both ends of specified parameter

trim () {
    read -rd '' $1 <<<"${!1}"
}

# Unit test for trim()

test_trim () {
    local foo="$1"
    trim foo
    test "$foo" = "$2"
}

test_trim hey hey &&
test_trim '  hey' hey &&
test_trim 'ho  ' ho &&
test_trim 'hey ho' 'hey ho' &&
test_trim '  hey  ho  ' 'hey  ho' &&
test_trim $'\n\n\t hey\n\t ho \t\n' $'hey\n\t ho' &&
test_trim $'\n' '' &&
test_trim '\n' '\n' &&
echo passed
flabdablet
sumber
2
Luar biasa! Sederhana dan efektif! Jelas solusi favorit saya. Terima kasih!
xebeche
1
@CraigMcQueen itu adalah nilai variabel, seperti yang readakan disimpan di variabel dengan namanya $ 1 versi yang dipangkas nilainya $ {! 1}
Aquarius Power
2
Parameter fungsi trim () adalah nama variabel: lihat panggilan untuk memotong () di dalam test_trim (). Dalam trim () sebagaimana dipanggil dari test_trim (), $ 1 berekspansi ke foo dan $ {! 1} berekspansi ke $ foo (yaitu, ke isi variabel foo saat ini). Cari manual bash untuk 'tipuan variabel'.
flabdablet
1
Bagaimana dengan modifikasi kecil ini, untuk mendukung pemangkasan beberapa vars dalam satu panggilan? trim() { while [[ $# -gt 0 ]]; do read -rd '' $1 <<<"${!1}"; shift; done; }
Gene Pavlovsky
2
@AquariusPower tidak perlu menggunakan gema dalam subkulit untuk versi satu-liner, cukup read -rd '' str <<<"$str"lakukan.
flabdablet
12

Ada banyak jawaban, tetapi saya masih percaya naskah saya yang baru saja ditulis layak disebutkan karena:

  • itu berhasil diuji di shell bash / dash / busybox shell
  • ini sangat kecil
  • itu tidak tergantung pada perintah eksternal dan tidak perlu melakukan fork (-> penggunaan sumber daya yang cepat dan rendah)
  • ini berfungsi seperti yang diharapkan:
    • itu menghapus semua spasi dan tab dari awal dan akhir, tetapi tidak lebih
    • Penting: itu tidak menghapus apa pun dari tengah-tengah string (banyak jawaban lain lakukan), bahkan baris baru akan tetap
    • spesial: "$*"gabungan beberapa argumen menggunakan satu spasi. jika Anda ingin memotong & hanya menampilkan argumen pertama, gunakan "$1"saja
    • jika tidak memiliki masalah dengan pola nama file yang cocok dll

Naskah:

trim() {
  local s2 s="$*"
  until s2="${s#[[:space:]]}"; [ "$s2" = "$s" ]; do s="$s2"; done
  until s2="${s%[[:space:]]}"; [ "$s2" = "$s" ]; do s="$s2"; done
  echo "$s"
}

Pemakaian:

mystring="   here     is
    something    "
mystring=$(trim "$mystring")
echo ">$mystring<"

Keluaran:

>here     is
    something<
Daniel Alder
sumber
Bah dalam C ini akan menjadi cara yang lebih sederhana untuk diterapkan!
Nils
Tentu. Sayangnya, ini bukan C dan kadang-kadang Anda ingin menghindari memanggil alat eksternal
Daniel Alder
Untuk membuat kode lebih mudah dibaca dan salin-masa lalu, Anda dapat mengubah tanda kurung menjadi karakter yang lolos:[\ \t]
leondepeon
@leondepeon apakah Anda mencoba ini? Saya mencoba ketika saya menulisnya dan mencoba lagi, dan saran Anda tidak bekerja di bash, dash, busybox
Daniel Alder
@DanielAlder saya lakukan, tetapi karena sudah 3 tahun yang lalu, saya tidak dapat menemukan kode di mana saya menggunakannya. Namun sekarang, saya mungkin akan menggunakan [[:space:]]seperti di salah satu jawaban lain: stackoverflow.com/a/3352015/3968618
leondepeon
11

Anda bisa menggunakan old-school tr. Sebagai contoh, ini mengembalikan jumlah file yang dimodifikasi dalam repositori git, spasi putih dilucuti.

MYVAR=`git ls-files -m|wc -l|tr -d ' '`
pojo
sumber
1
Ini tidak memangkas spasi putih dari depan dan belakang - ini menghapus semua spasi putih dari string.
Nick
11

Ini bekerja untuk saya:

text="   trim my edges    "

trimmed=$text
trimmed=${trimmed##+( )} #Remove longest matching series of spaces from the front
trimmed=${trimmed%%+( )} #Remove longest matching series of spaces from the back

echo "<$trimmed>" #Adding angle braces just to make it easier to confirm that all spaces are removed

#Result
<trim my edges>

Untuk membuatnya lebih sedikit pada baris untuk hasil yang sama:

text="    trim my edges    "
trimmed=${${text##+( )}%%+( )}
gmale
sumber
1
Tidak bekerja untuk saya. Yang pertama mencetak string yang tidak dipotong. Yang kedua melemparkan substitusi yang buruk. Bisakah Anda jelaskan apa yang terjadi di sini?
musicin3d
1
@ musicin3d: ini adalah situs yang sering saya gunakan yang menjelaskan cara kerja manipulasi variabel dalam pencarian bash${var##Pattern} untuk detail lebih lanjut. Juga, situs ini menjelaskan pola bash . Jadi ##cara menghapus pola yang diberikan dari depan dan %%berarti menghapus pola yang diberikan dari belakang. The +( )porsi pola dan itu berarti "satu atau lebih terjadinya ruang"
gMale
Lucu, itu berfungsi pada prompt, tetapi tidak setelah mentransposisi ke file skrip bash.
Dr Beco
aneh. Apakah ini versi bash yang sama di kedua contoh?
gMale
11
# Strip leading and trailing white space (new line inclusive).
trim(){
    [[ "$1" =~ [^[:space:]](.*[^[:space:]])? ]]
    printf "%s" "$BASH_REMATCH"
}

ATAU

# Strip leading white space (new line inclusive).
ltrim(){
    [[ "$1" =~ [^[:space:]].* ]]
    printf "%s" "$BASH_REMATCH"
}

# Strip trailing white space (new line inclusive).
rtrim(){
    [[ "$1" =~ .*[^[:space:]] ]]
    printf "%s" "$BASH_REMATCH"
}

# Strip leading and trailing white space (new line inclusive).
trim(){
    printf "%s" "$(rtrim "$(ltrim "$1")")"
}

ATAU

# Strip leading and trailing specified characters.  ex: str=$(trim "$str" $'\n a')
trim(){
    if [ "$2" ]; then
        trim_chrs="$2"
    else
        trim_chrs="[:space:]"
    fi

    [[ "$1" =~ ^["$trim_chrs"]*(.*[^"$trim_chrs"])["$trim_chrs"]*$ ]]
    printf "%s" "${BASH_REMATCH[1]}"
}

ATAU

# Strip leading specified characters.  ex: str=$(ltrim "$str" $'\n a')
ltrim(){
    if [ "$2" ]; then
        trim_chrs="$2"
    else
        trim_chrs="[:space:]"
    fi

    [[ "$1" =~ ^["$trim_chrs"]*(.*[^"$trim_chrs"]) ]]
    printf "%s" "${BASH_REMATCH[1]}"
}

# Strip trailing specified characters.  ex: str=$(rtrim "$str" $'\n a')
rtrim(){
    if [ "$2" ]; then
        trim_chrs="$2"
    else
        trim_chrs="[:space:]"
    fi

    [[ "$1" =~ ^(.*[^"$trim_chrs"])["$trim_chrs"]*$ ]]
    printf "%s" "${BASH_REMATCH[1]}"
}

# Strip leading and trailing specified characters.  ex: str=$(trim "$str" $'\n a')
trim(){
    printf "%s" "$(rtrim "$(ltrim "$1" "$2")" "$2")"
}

ATAU

Membangun di atas soulution exp ...

# Strip leading and trailing white space (new line inclusive).
trim(){
    printf "%s" "`expr "$1" : "^[[:space:]]*\(.*[^[:space:]]\)[[:space:]]*$"`"
}

ATAU

# Strip leading white space (new line inclusive).
ltrim(){
    printf "%s" "`expr "$1" : "^[[:space:]]*\(.*[^[:space:]]\)"`"
}

# Strip trailing white space (new line inclusive).
rtrim(){
    printf "%s" "`expr "$1" : "^\(.*[^[:space:]]\)[[:space:]]*$"`"
}

# Strip leading and trailing white space (new line inclusive).
trim(){
    printf "%s" "$(rtrim "$(ltrim "$1")")"
}
NOYB
sumber
8

Saya telah melihat skrip hanya menggunakan tugas variabel untuk melakukan pekerjaan:

$ xyz=`echo -e 'foo \n bar'`
$ echo $xyz
foo bar

Ruang kosong secara otomatis digabungkan dan dipangkas. Kita harus berhati-hati terhadap metakarakter shell (risiko injeksi potensial).

Saya juga merekomendasikan untuk selalu memberikan dua kali penggantian variabel dalam kondisi shell:

if [ -n "$var" ]; then

karena sesuatu seperti -o atau konten lain dalam variabel dapat mengubah argumen pengujian Anda.

MykennaC
sumber
3
Ini adalah penggunaan kuotasi dari $xyzdengan echoyang melakukan penggabungan spasi, bukan variabel tugas. Untuk menyimpan nilai yang dipangkas dalam variabel dalam contoh Anda, Anda harus menggunakan xyz=$(echo -n $xyz). Juga, pendekatan ini tunduk pada ekspansi pathname yang mungkin tidak diinginkan (globbing).
mklement0
ini menonjol salah, nilai dalam xyzvariabel TIDAK dipangkas.
caesarsol
7
var='   a b c   '
trimmed=$(echo $var)
ultr
sumber
1
Itu tidak akan berhasil jika ada lebih dari satu ruang di antara dua kata. Coba: echo $(echo "1 2 3")(dengan dua spasi antara 1, 2, dan 3).
joshlf
7

Saya hanya akan menggunakan sed:

function trim
{
    echo "$1" | sed -n '1h;1!H;${;g;s/^[ \t]*//g;s/[ \t]*$//g;p;}'
}

a) Contoh penggunaan pada string baris tunggal

string='    wordA wordB  wordC   wordD    '
trimmed=$( trim "$string" )

echo "GIVEN STRING: |$string|"
echo "TRIMMED STRING: |$trimmed|"

Keluaran:

GIVEN STRING: |    wordA wordB  wordC   wordD    |
TRIMMED STRING: |wordA wordB  wordC   wordD|

b) Contoh penggunaan pada string multi-line

string='    wordA
   >wordB<
wordC    '
trimmed=$( trim "$string" )

echo -e "GIVEN STRING: |$string|\n"
echo "TRIMMED STRING: |$trimmed|"

Keluaran:

GIVEN STRING: |    wordAA
   >wordB<
wordC    |

TRIMMED STRING: |wordAA
   >wordB<
wordC|

c) Catatan akhir:
Jika Anda tidak suka menggunakan fungsi, untuk string baris tunggal Anda cukup menggunakan perintah "lebih mudah diingat" seperti:

echo "$string" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//'

Contoh:

echo "   wordA wordB wordC   " | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//'

Keluaran:

wordA wordB wordC

Menggunakan hal di atas pada string multi-line akan berfungsi juga , tetapi harap dicatat bahwa itu akan memotong semua ruang trailing / leading internal juga, seperti yang GuruM perhatikan dalam komentar

string='    wordAA
    >four spaces before<
 >one space before<    '
echo "$string" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//'

Keluaran:

wordAA
>four spaces before<
>one space before<

Jadi, jika Anda berkeinginan untuk menjaga jarak tersebut, silakan gunakan fungsi ini di awal jawaban saya!

d) PENJELASAN sintaks sed "find and replace" pada string multi-line yang digunakan di dalam fungsi trim:

sed -n '
# If the first line, copy the pattern to the hold buffer
1h
# If not the first line, then append the pattern to the hold buffer
1!H
# If the last line then ...
$ {
    # Copy from the hold to the pattern buffer
    g
    # Do the search and replace
    s/^[ \t]*//g
    s/[ \t]*$//g
    # print
    p
}'
Luca Borrione
sumber
Catatan: Seperti yang disarankan oleh @mkelement, itu tidak akan berfungsi untuk string multi-line meskipun harus berfungsi untuk string single-line.
GuruM
1
Anda salah: itu berfungsi pada string multi-line juga. Coba saja! :)
Luca Borrione
+1 untuk penggunaan - membuatnya mudah bagi saya untuk menguji kode. Namun kode tersebut masih tidak berfungsi untuk string multi-line. Jika Anda perhatikan dengan teliti pada output, Anda akan melihat bahwa setiap / tertinggal terkemuka ruang internal juga mendapatkan dihapus misalnya ruang di depan "multi-line" diganti dengan "multi-line". Coba saja tambahkan jumlah spasi awal / akhir di setiap baris.
GuruM
Sekarang saya mengerti maksud Anda! Terima kasih atas jawabannya, saya mengedit jawaban saya.
Luca Borrione
@ "Luca Borrione" - selamat datang :-) Apakah Anda akan menjelaskan sintaks sed yang Anda gunakan dalam trim ()? Ini juga dapat membantu pengguna kode Anda untuk mengubah dengan penggunaan lain. Juga mungkin membantu menemukan kasus tepi untuk ekspresi reguler.
GuruM
6

Inilah fungsi trim () yang memotong dan menormalkan spasi putih

#!/bin/bash
function trim {
    echo $*
}

echo "'$(trim "  one   two    three  ")'"
# 'one two three'

Dan varian lain yang menggunakan ekspresi reguler.

#!/bin/bash
function trim {
    local trimmed="$@"
    if [[ "$trimmed" =~ " *([^ ].*[^ ]) *" ]]
    then 
        trimmed=${BASH_REMATCH[1]}
    fi
    echo "$trimmed"
}

echo "'$(trim "  one   two    three  ")'"
# 'one   two    three'
Nicholas Sushkin
sumber
Pendekatan pertama rumit karena tidak hanya menormalkan spasi putih interior (menggantikan semua rentang spasi putih dengan masing-masing satu ruang), tetapi juga tunduk pada globbing (ekspansi nama path) sehingga, misalnya, *karakter dalam string input akan perluas ke semua file dan folder di folder yang aktif saat ini. Akhirnya, jika $ IFS diatur ke nilai non-default, pemangkasan mungkin tidak berfungsi (meskipun mudah diperbaiki dengan menambahkan local IFS=$' \t\n'). Pemotongan terbatas pada bentuk spasi putih berikut: spasi, \tdan \nkarakter.
mklement0
1
Yang kedua, pendekatan berbasis ekspresi reguler adalah bagus dan bebas efek samping, tetapi dalam bentuknya yang sekarang bermasalah: (a) pada bash v3.2 +, pencocokan secara default TIDAK akan berfungsi, karena ekspresi reguler harus tidak dikutip dalam urutan untuk bekerja dan (b) ekspresi reguler itu sendiri tidak menangani kasus di mana string input adalah karakter tunggal, bukan spasi yang dikelilingi oleh spasi. Untuk memperbaiki masalah ini, ganti ifsesuai dengan: if [[ "$trimmed" =~ ' '*([^ ]|[^ ].*[^ ])' '* ]]. Akhirnya, pendekatan ini hanya berurusan dengan spasi, bukan bentuk spasi putih lainnya (lihat komentar saya selanjutnya).
mklement0
2
Fungsi yang memanfaatkan ekspresi reguler hanya berurusan dengan spasi dan bukan bentuk spasi putih lainnya, tetapi mudah untuk menggeneralisasi: Ganti ifbaris dengan:[[ "$trimmed" =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]
mklement0
6

Gunakan AWK:

echo $var | awk '{gsub(/^ +| +$/,"")}1'
ghostdog74
sumber
Manis yang sepertinya bekerja (mis :) $stripped_version=gema $ var | awk '{gsub (/ ^ + | + $ /, "")} 1'``
rogerdpack
4
kecuali awk tidak melakukan apa-apa: echo'ing variabel yang tidak dikutip telah menghapus spasi
glenn jackman
6

Tugas mengabaikan spasi putih terkemuka dan tertinggal dan karenanya dapat digunakan untuk memotong:

$ var=`echo '   hello'`; echo $var
hello
evanx
sumber
8
Itu tidak benar. Itu "gema" yang menghapus spasi, bukan tugas. Dalam contoh Anda, lakukan echo "$var"untuk melihat nilai dengan spasi.
Nicholas Sushkin
2
@NicholasSushkin One bisa melakukannya var=$(echo $var)tetapi saya tidak merekomendasikannya. Solusi lain yang disajikan di sini lebih disukai.
xebeche
5

Ini tidak memiliki masalah dengan globbing yang tidak diinginkan, juga, ruang putih interior tidak dimodifikasi (dengan asumsi yang $IFSdiatur ke default, yaitu ' \t\n').

Itu membaca hingga baris baru pertama (dan tidak termasuk itu) atau akhir string, mana yang lebih dulu, dan menghapus setiap campuran ruang dan \tkarakter memimpin dan tertinggal . Jika Anda ingin mempertahankan beberapa baris (dan juga menghapus dan memimpin baris baru), gunakan read -r -d '' var << eofsebaliknya; Perhatikan, bagaimanapun, bahwa jika input Anda mengandung \neof, itu akan dipotong sebelum. (Bentuk ruang putih lainnya, yaitu \r,, \fdan \v, tidak dilucuti, bahkan jika Anda menambahkannya ke $ IFS.)

read -r var << eof
$var
eof
Gregor
sumber
5

Ini akan menghapus semua spasi putih dari String Anda,

 VAR2="${VAR2//[[:space:]]/}"

/menggantikan kemunculan pertama dan //semua kemunculan spasi putih dalam string. Yaitu semua ruang putih bisa diganti dengan - tidak ada

Alpesh Gediya
sumber
4

Ini adalah metode paling sederhana yang pernah saya lihat. Hanya menggunakan Bash, hanya beberapa baris, regexp sederhana, dan cocok dengan semua bentuk spasi putih:

if [[ "$test" =~ ^[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$ ]]
then 
    test=${BASH_REMATCH[1]}
fi

Berikut ini contoh skrip untuk mengujinya:

test=$(echo -e "\n \t Spaces and tabs and newlines be gone! \t  \n ")

echo "Let's see if this works:"
echo
echo "----------"
echo -e "Testing:${test} :Tested"  # Ugh!
echo "----------"
echo
echo "Ugh!  Let's fix that..."

if [[ "$test" =~ ^[[:space:]]*([^[:space:]].*[^[:space:]])[[:space:]]*$ ]]
then 
    test=${BASH_REMATCH[1]}
fi

echo
echo "----------"
echo -e "Testing:${test}:Tested"  # "Testing:Spaces and tabs and newlines be gone!"
echo "----------"
echo
echo "Ah, much better."
blujay
sumber
1
Tentunya lebih disukai untuk, misalnya (kamu tuhan!), Keluar ke Python. Kecuali saya pikir itu lebih sederhana dan lebih umum untuk secara benar menangani string yang hanya berisi spasi. Ekspresi yang disederhanakan adalah:^[[:space:]]*(.*[^[:space:]])?[[:space:]]*$
Ron Burk
4

Python memiliki fungsi strip()yang identik dengan PHP trim(), jadi kita bisa melakukan sedikit inline Python untuk membuat utilitas yang mudah dimengerti untuk ini:

alias trim='python -c "import sys; sys.stdout.write(sys.stdin.read().strip())"'

Ini akan memangkas spasi putih depan dan akhir (termasuk baris baru).

$ x=`echo -e "\n\t   \n" | trim`
$ if [ -z "$x" ]; then echo hi; fi
hi
brownhead
sumber
sementara itu bekerja, Anda mungkin ingin mempertimbangkan untuk menawarkan solusi yang tidak melibatkan meluncurkan juru bahasa python penuh hanya untuk memotong string. Itu hanya boros.
pdwalker
3
#!/bin/bash

function trim
{
    typeset trimVar
    eval trimVar="\${$1}"
    read trimVar << EOTtrim
    $trimVar
EOTtrim
    eval $1=\$trimVar
}

# Note that the parameter to the function is the NAME of the variable to trim, 
# not the variable contents.  However, the contents are trimmed.


# Example of use:
while read aLine
do
    trim aline
    echo "[${aline}]"
done < info.txt



# File info.txt contents:
# ------------------------------
# ok  hello there    $
#    another  line   here     $
#and yet another   $
#  only at the front$
#$



# Output:
#[ok  hello there]
#[another  line   here]
#[and yet another]
#[only at the front]
#[]
Razor5900
sumber
3

Saya menemukan bahwa saya perlu menambahkan beberapa kode dari sdiffkeluaran yang berantakan untuk membersihkannya:

sdiff -s column1.txt column2.txt | grep -F '<' | cut -f1 -d"<" > c12diff.txt 
sed -n 1'p' c12diff.txt | sed 's/ *$//g' | tr -d '\n' | tr -d '\t'

Ini menghilangkan spasi tambahan dan karakter tak terlihat lainnya.

pengguna1186515
sumber
3

Menghapus spasi ke satu ruang:

(text) | fmt -su
gardziol
sumber