Làm thế nào để cắt khoảng trắng từ một biến Bash?


922

Tôi có một kịch bản shell với mã này:

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

Nhưng mã điều kiện luôn luôn thực thi, bởi vì hg stluôn in ít nhất một ký tự dòng mới.

  • Có cách nào đơn giản để loại bỏ khoảng trắng từ $var(như trim()trong PHP ) không?

hoặc là

  • Có một cách tiêu chuẩn để đối phó với vấn đề này?

Tôi có thể sử dụng sed hoặc AWK , nhưng tôi muốn nghĩ rằng có một giải pháp thanh lịch hơn cho vấn đề này.


3
Liên quan, nếu bạn muốn cắt không gian trên một số nguyên và chỉ lấy số nguyên, bọc bằng $ (($ var)) và thậm chí có thể làm điều đó khi bên trong dấu ngoặc kép. Điều này trở nên quan trọng khi tôi sử dụng tuyên bố ngày và với tên tệp.
Volomike

"Có một cách tiêu chuẩn để đối phó với vấn đề này?" Có, sử dụng [[thay vì [. $ var=$(echo) $ [ -n $var ]; echo $? #undesired test return 0 $ [[ -n $var ]]; echo $? 1
user.friendly

Nếu nó giúp, ít nhất là nơi đang thử nghiệm nó trên Ubuntu 16.04. Sử dụng các kết hợp sau đây theo mọi cách : echo " This is a string of char " | xargs. Tuy nhiên, nếu bạn có một trích dẫn trong văn bản, bạn có thể làm như sau : echo " This i's a string of char " | xargs -0. Lưu ý rằng tôi đề cập đến xargs mới nhất (4.6.0)
Luis Alvarado

Điều kiện này không đúng vì một dòng mới khi backticks nuốt dòng mới cuối cùng. Điều này sẽ không in gì test=`echo`; if [ -n "$test" ]; then echo "Not empty"; fi, tuy nhiên điều này sẽ test=`echo "a"`; if [ -n "$test" ]; then echo "Not empty"; fi- vì vậy phải có nhiều hơn chỉ là một dòng mới ở cuối.
Mecki

A = "123 4 5 6"; B = echo $A | sed -r 's/( )+//g';
bruziuz

Câu trả lời:


1022

Hãy xác định một biến chứa khoảng trắng hàng đầu, dấu và trung gian:

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

Cách xóa tất cả khoảng trắng (ký hiệu là [:space:]trong 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

Cách xóa chỉ khoảng trắng hàng đầu:

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

Cách xóa chỉ khoảng trắng theo sau:

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

Cách xóa cả dấu cách hàng đầu và dấu cách - xâu chuỗi seds:

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

Ngoài ra, nếu bash của bạn hỗ trợ nó, bạn có thể thay thế echo -e "${FOO}" | sed ...bằng sed ... <<<${FOO}, như vậy (đối với khoảng trắng theo sau):

FOO_NO_TRAIL_SPACE="$(sed -e 's/[[:space:]]*$//' <<<${FOO})"

63
Để khái quát hóa giải pháp xử lý tất cả các dạng khoảng trắng, hãy thay thế ký tự khoảng trắng trong trsedcác lệnh bằng [[:space:]]. Lưu ý rằng sedphương pháp này sẽ chỉ hoạt động trên đầu vào một dòng . Đối với các phương pháp hoạt động với đầu vào nhiều dòng và cũng sử dụng các tính năng tích hợp của bash, hãy xem câu trả lời của @bashfu và @GuruM. Một phiên bản nội tuyến, tổng quát của giải pháp @Nicholas Sushkin sẽ giống như thế này: trimmed=$([[ " test test test " =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]; echo -n "${BASH_REMATCH[1]}")
mkuity0

7
Nếu bạn làm điều đó thường xuyên, việc thêm alias trim="sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*\$//g'"vào ~/.profilecho phép bạn sử dụng echo $SOMEVAR | trimcat somefile | trim.
ví dụ tôi

Tôi đã viết một sedgiải pháp chỉ sử dụng một biểu thức duy nhất chứ không phải hai : sed -r 's/^\s*(\S+(\s+\S+)*)\s*$/\1/'. Nó cắt các khoảng trắng hàng đầu và dấu, và ghi lại bất kỳ chuỗi phân tách khoảng trắng nào của các ký tự không khoảng trắng ở giữa. Thưởng thức!
Victor Zamanian

@VictorZamanian Giải pháp của bạn không hoạt động nếu đầu vào chỉ chứa khoảng trắng. Các giải pháp sed hai mẫu được đưa ra bởi MattyV và dụ tôi hoạt động tốt với đầu vào chỉ khoảng trắng.
Torben

@Torben Điểm công bằng. Tôi cho rằng biểu thức đơn có thể được tạo thành có điều kiện, với |, để giữ cho nó là một biểu thức, thay vì một biểu thức.
Victor Zamanian

966

Một câu trả lời đơn giản là:

echo "   lol  " | xargs

Xargs sẽ làm việc cắt tỉa cho bạn. Đó là một lệnh / chương trình, không có tham số, trả về chuỗi được cắt xén, dễ dàng như vậy!

Lưu ý: điều này không loại bỏ tất cả các không gian nội bộ để "foo bar"giữ nguyên; nó KHÔNG trở thành "foobar". Tuy nhiên, nhiều không gian sẽ được cô đọng thành các không gian duy nhất, do đó "foo bar"sẽ trở thành "foo bar". Ngoài ra, nó không loại bỏ cuối dòng ký tự.


27
Đẹp. Điều này hoạt động thực sự tốt. Tôi đã quyết định chuyển nó thành xargs echochỉ để nói dài dòng về những gì tôi đang làm, nhưng xargs tự nó sẽ sử dụng echo theo mặc định.
Sẽ

24
Thủ thuật hay, nhưng hãy cẩn thận, bạn có thể sử dụng nó cho chuỗi một dòng nhưng by xargs thiết kế nó sẽ không chỉ cắt xén với nội dung đa đường. sed là bạn của bạn rồi
Jocelyn delalande

22
Vấn đề duy nhất với xargs là nó sẽ giới thiệu một dòng mới, nếu bạn muốn tắt dòng mới, tôi muốn giới thiệu sed 's/ *$//'như một giải pháp thay thế. Bạn có thể xem xargsxuống dòng như thế này: echo -n "hey thiss " | xargs | hexdump bạn sẽ nhận thấy 0a73sự alà dòng mới. Nếu bạn làm tương tự với sed: echo -n "hey thiss " | sed 's/ *$//' | hexdumpbạn sẽ thấy 0073, không có dòng mới.

8
Cẩn thận; điều này sẽ phá vỡ khó khăn nếu chuỗi thành xargs chứa khoảng trắng dư thừa ở giữa. Giống như "đây là một đối số". xargs sẽ chia thành bốn.
bos

64
Thật tệ. 1. Nó sẽ biến a<space><space>bthành a<space>b. 2. Thậm chí nhiều hơn: nó sẽ biến a"b"c'd'ethành abcde. 3. Thậm chí nhiều hơn: nó sẽ thất bại a"b, v.v.
Sasha

359

Có một giải pháp chỉ sử dụng Bash dựng sẵn được gọi là ký tự đại diện :

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

Đây là cùng một gói trong một chức năng:

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

Bạn vượt qua chuỗi được cắt ở dạng trích dẫn. ví dụ:

trim "   abc   "

Một điều tuyệt vời về giải pháp này là nó sẽ hoạt động với mọi vỏ tương thích POSIX.

Tài liệu tham khảo


18
Tài giỏi! Đây là giải pháp yêu thích của tôi vì nó sử dụng chức năng bash tích hợp. Cảm ơn vì đăng! @San, đó là hai chuỗi xâu lồng nhau. Ví dụ, s=" 1 2 3 "; echo \""${s%1 2 3 }"\"sẽ cắt mọi thứ từ cuối, trả lại hàng đầu " ". Subbing 1 2 3 với [![:space:]]*bảo nó "tìm nhân vật không phải không gian đầu tiên, sau đó đóng băng nó và mọi thứ sau". Sử dụng %%thay vì %làm cho hoạt động cắt từ đầu cuối tham lam. Điều này được lồng trong một trim-không tham lam từ đầu, vì vậy trong thực tế, bạn cắt " "từ đầu. Sau đó, trao đổi%, # và * cho khoảng trắng kết thúc. Bế!
Đánh dấu G.

2
Tôi không tìm thấy bất kỳ tác dụng phụ không mong muốn nào và mã chính cũng hoạt động với các shell giống như POSIX khác. Tuy nhiên, trong Solaris 10, nó không hoạt động với /bin/sh(chỉ với /usr/xpg4/bin/sh, nhưng đây không phải là những gì sẽ được sử dụng với các tập lệnh sh thông thường).
vinc17

9
Giải pháp tốt hơn nhiều so với sử dụng sed, tr, v.v., vì nó nhanh hơn nhiều, tránh mọi ngã ba (). Trên Cygwin sự khác biệt về tốc độ là các đơn đặt hàng cường độ.
Gene Pavlovsky

9
@San Lúc đầu tôi bị bối rối vì nghĩ đây là những biểu hiện đều đặn. Họ không phải. Thay vào đó, đây là cú pháp Kết hợp mẫu ( gnu.org/software/bash/manual/html_node/Potype-Matching.html , wiki.bash-hackers.org/syntax/potype ) được sử dụng trong Loại bỏ chuỗi con ( tldp.org/LDP/abs /html/opes-manipulation.html ). Vì vậy, ${var%%[![:space:]]*}nói "loại bỏ khỏi varchuỗi con dài nhất của nó bắt đầu bằng một ký tự không phải không gian". Điều đó có nghĩa là bạn chỉ còn lại với (các) không gian hàng đầu mà sau đó bạn sẽ xóa ${var#... Các dòng sau (trailing) là ngược lại.
Ohad Schneider

8
Đây là giải pháp lý tưởng. Forking một hoặc bên ngoài nhiều quá trình (ví dụ awk, sed, tr, xargs) chỉ để khoảng trắng cắt từ một chuỗi duy nhất là về cơ bản điên rồ - đặc biệt là khi hầu hết vỏ (kể cả bash) đã cung cấp chuỗi nguồn gốc cơ sở munging out-of-the-box.
Cecil Curry

81

Bash có một tính năng gọi là mở rộng tham số , trong số những thứ khác, cho phép thay thế chuỗi dựa trên các mẫu được gọi là (các mẫu giống với các biểu thức thông thường, nhưng có những khác biệt và hạn chế cơ bản). [dòng gốc của flussence: Bash có các biểu thức chính quy, nhưng chúng được giấu kỹ:]

Dưới đây trình bày cách loại bỏ tất cả khoảng trắng (thậm chí từ bên trong) khỏi giá trị biến.

$ 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

2
Hay đúng hơn, nó hoạt động cho các không gian ở giữa một var, nhưng không phải khi tôi cố gắng neo nó ở cuối.
Paul Tomblin

Điều này có giúp được gì không? Từ trang chủ: "$ {tham số / mẫu / chuỗi} [...] Nếu mẫu bắt đầu bằng%, thì nó phải khớp ở cuối giá trị mở rộng của tham số."

@Ant, vì vậy chúng không thực sự là biểu thức chính quy, nhưng có gì tương tự?
Paul Tomblin

3
Họ là regex, chỉ là một phương ngữ lạ.

13
${var/ /}loại bỏ ký tự không gian đầu tiên. ${var// /}loại bỏ tất cả các ký tự không gian. Không có cách nào để cắt chỉ khoảng trắng hàng đầu và dấu chỉ với cấu trúc này.
Gilles 'SO- đừng trở nên xấu xa'

60

Để xóa tất cả các khoảng trắng từ đầu và cuối chuỗi (bao gồm cả các ký tự cuối dòng):

echo $variable | xargs echo -n

Điều này cũng sẽ loại bỏ các không gian trùng lặp:

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

Sản xuất: 'chuỗi này có nhiều khoảng trắng'


5
Về cơ bản các xargs loại bỏ tất cả các dấu phân cách khỏi chuỗi. Theo mặc định, nó sử dụng khoảng trắng làm dấu phân cách (điều này có thể được thay đổi bởi tùy chọn -d).
rkachach

4
Đây là giải pháp sạch nhất (cả ngắn và dễ đọc).
Potherca

Tại sao bạn cần echo -ntất cả? echo " my string " | xargscó cùng sản lượng.
bfontaine

echo -n cũng xóa phần cuối của dòng
rkachach 20/03/19

55

Tách một không gian hàng đầu và một dấu

trim()
{
    local trimmed="$1"

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

    echo "$trimmed"
}

Ví dụ:

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

Đầu ra:

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

Tách tất cả các không gian hàng đầu và dấu

trim()
{
    local trimmed="$1"

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

    echo "$trimmed"
}

Ví dụ:

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

Đầu ra:

'two leading', 'two trailing', 'two leading and two trailing'

9
Điều này sẽ chỉ cắt 1 ký tự không gian. Vì vậy, tiếng vang kết quả trong'hello world ', 'foo bar', 'both sides '
Joe

@Joe Tôi đã thêm một lựa chọn tốt hơn.
wjandrea

42

Từ phần Bash Guide trên globalbing

Để sử dụng một extglob trong một mở rộng tham số

 #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  

Đây là chức năng tương tự được bao bọc trong một chức năng (LƯU Ý: Cần trích dẫn chuỗi đầu vào được truyền cho chức năng):

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.
}

Sử dụng:

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

Nếu chúng ta thay đổi hàm để thực thi trong một lớp con, chúng ta không phải lo lắng về việc kiểm tra tùy chọn shell hiện tại cho extglob, chúng ta chỉ có thể đặt nó mà không ảnh hưởng đến shell hiện tại. Điều này đơn giản hóa các chức năng rất nhiều. Tôi cũng cập nhật các tham số vị trí "tại chỗ" vì vậy tôi thậm chí không cần một biến cục bộ

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

vì thế:

$ 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

2
như bạn đã quan sát trim () chỉ xóa khoảng trắng hàng đầu và dấu.
Giáo sư

Như mkelement đã lưu ý, bạn cần truyền tham số hàm dưới dạng chuỗi được trích dẫn, ví dụ $ (trim "$ string") thay vì $ (trim $ string). Tôi đã cập nhật mã để hiển thị cách sử dụng chính xác. Cảm ơn.
Giáo sư

Nhiều như tôi đánh giá cao việc biết về các tùy chọn vỏ, tôi không nghĩ rằng kết quả cuối cùng thanh lịch hơn là chỉ thực hiện 2 thay thế mẫu
sehe

Lưu ý rằng (với một phiên bản đủ gần đây của Bash?), Bạn có thể đơn giản hóa cơ chế khôi phục tùy chọn extglob, bằng cách sử dụng shopt -p: chỉ cần viết local restore="$(shopt -p extglob)" ; shopt -s extglobở đầu chức năng của bạn và eval "$restore"ở cuối (ngoại trừ, được cấp, eval là tà ác).
Maëlan

Giải pháp tuyệt vời! Một cải tiến tiềm năng: có vẻ như [[:space:]]có thể được thay thế bằng, tốt, một không gian: ${var##+( )}và cũng ${var%%+( )}hoạt động và chúng dễ đọc hơn.
DKroot

40

Bạn có thể cắt đơn giản với echo:

foo=" qsdqsd qsdqs q qs   "

# Not trimmed
echo \'$foo\'

# Trim
foo=`echo $foo`

# Trimmed
echo \'$foo\'

Điều này thu gọn nhiều không gian tiếp giáp thành một.
Evgeni Sergeev 16/12/13

7
Bạn đã thử nó khi foochứa một ký tự đại diện? ví dụ: foo=" I * have a wild card"... bất ngờ! Hơn nữa, điều này thu gọn một số không gian tiếp giáp thành một.
gniourf_gniourf

5
Đây là một giải pháp tuyệt vời nếu bạn: 1. muốn không có khoảng trắng ở cuối 2. chỉ muốn một khoảng trắng giữa mỗi từ 3. đang hoạt động với đầu vào được kiểm soát không có ký tự đại diện. Về cơ bản, nó biến một danh sách được định dạng xấu thành một danh sách tốt.
musicin3d

Lời nhắc tốt về các ký tự đại diện @gniourf_gniourf +1. Vẫn là một giải pháp xuất sắc, Vamp. +1 cho bạn quá.
Bác sĩ Beco

25

Tôi đã luôn luôn làm nó với sed

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

Nếu có một giải pháp thanh lịch hơn, tôi hy vọng ai đó đăng nó.


bạn có thể giải thích cú pháp cho sed?
farid99

2
Biểu thức chính quy khớp với tất cả các khoảng trắng ở cuối và thay thế nó bằng không có gì.
Paul Tomblin

4
Làm thế nào về khoảng trắng hàng đầu?
Qian Chen

Điều này dải tất cả khoảng trắng trailing sed -e 's/\s*$//'. Giải thích: 's' có nghĩa là tìm kiếm, '\ s' có nghĩa là tất cả khoảng trắng, '*' có nghĩa là không hoặc nhiều, '$' có nghĩa là đến cuối dòng và '//' có nghĩa là thay thế tất cả các kết quả khớp bằng một chuỗi trống .
Craig

Trong 's / * $ //', tại sao có 2 dấu cách trước dấu hoa thị, thay vì một khoảng trắng? Đó có phải là một lỗi đánh máy?
Brent212

24

Bạn có thể xóa các dòng mới với tr:

var=`hg st -R "$path" | tr -d '\n'`
if [ -n $var ]; then
    echo $var
done

8
Tôi không muốn xóa '\ n' ở giữa chuỗi, chỉ từ đầu hoặc cuối.
quá nhiều php

24

Với các tính năng khớp mẫu mở rộng của Bash được bật ( shopt -s extglob), bạn có thể sử dụng tính năng này:

{trimmed##*( )}

để loại bỏ một lượng không gian hàng đầu tùy ý.


Khủng khiếp! Tôi nghĩ rằng đây là giải pháp nhẹ và thanh lịch nhất.
dubiousjim

1
Xem bài đăng của @ GuruM dưới đây để biết một giải pháp tương tự nhưng chung chung hơn (a) liên quan đến tất cả các dạng của khoảng trắng và (b) cũng xử lý khoảng trắng ở cuối .
mkuity0

@mkelement +1 vì đã gặp khó khăn khi viết lại đoạn mã của tôi dưới dạng hàm. Cảm ơn
GuruM

Hoạt động với mặc định / bin / ksh của OpenBSD. /bin/sh -o posixhoạt động quá nhưng tôi nghi ngờ.
Clint Pachl 16/03/18

Không phải là một bash wizard ở đây; những gì trimmed? Nó là một thứ tích hợp hoặc biến đang được cắt?
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

2
Kinh ngạc! Đơn giản và hiệu quả! Rõ ràng giải pháp yêu thích của tôi. Cảm ơn bạn!
xebeche

1
@CraigMcQueen nó là giá trị biến, vì readsẽ lưu trữ tại biến bằng tên của nó $ 1 một phiên bản được cắt bớt của giá trị của nó $ {! 1}
Aquarius Power

2
Tham số của hàm trim () là một tên biến: xem lệnh gọi trim () bên trong test_trim (). Trong trim () như được gọi từ test_trim (), $ 1 mở rộng thành foo và $ {! 1} mở rộng thành $ foo (nghĩa là, đối với nội dung hiện tại của biến foo). Tìm kiếm hướng dẫn bash cho 'biến đổi'.
flabdablet

1
Làm thế nào về sửa đổi nhỏ này, để hỗ trợ cắt tỉa nhiều vars trong một cuộc gọi? trim() { while [[ $# -gt 0 ]]; do read -rd '' $1 <<<"${!1}"; shift; done; }
Gene Pavlovsky

2
@AquariusPower không cần sử dụng echo trong một subshell cho phiên bản một lớp, chỉ cần read -rd '' str <<<"$str"làm.
flabdablet

12

Có rất nhiều câu trả lời, nhưng tôi vẫn tin rằng kịch bản vừa viết của mình đáng được nhắc đến vì:

  • nó đã được thử nghiệm thành công trong shell bash / dash / busybox shell
  • nó rất nhỏ
  • nó không phụ thuộc vào các lệnh bên ngoài và không cần rẽ nhánh (-> sử dụng tài nguyên nhanh và thấp)
  • nó hoạt động như mong đợi:
    • nó loại bỏ tất cả các khoảng trắng và tab từ đầu đến cuối, nhưng không nhiều hơn
    • quan trọng: nó không xóa bất cứ điều gì từ giữa chuỗi (nhiều câu trả lời khác làm), thậm chí các dòng mới sẽ vẫn còn
    • đặc biệt: "$*"tham gia nhiều đối số bằng một khoảng trắng. nếu bạn muốn cắt và chỉ xuất ra đối số đầu tiên, "$1"thay vào đó hãy sử dụng
    • nếu không có bất kỳ vấn đề nào với các mẫu tên tệp phù hợp, v.v.

Kịch bản:

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"
}

Sử dụng:

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

Đầu ra:

>here     is
    something<

Bah trong C này sẽ đơn giản hơn để thực hiện!
Nils

Chắc chắn rồi. Thật không may, đây không phải là C và đôi khi bạn muốn tránh gọi các công cụ bên ngoài
Daniel Alder

Để làm cho mã dễ đọc hơn và tương thích với quá khứ sao chép, bạn có thể thay đổi dấu ngoặc thành các ký tự thoát:[\ \t]
leondepeon

@leondepeon bạn đã thử chưa? Tôi đã thử khi tôi viết nó và thử lại, và đề xuất của bạn không hoạt động trong bất kỳ bash, dash, busybox nào
Daniel Alder

@DanielAlder Tôi đã làm, nhưng vì đã 3 năm rồi, tôi không thể tìm thấy mã nơi tôi đã sử dụng nó. Tuy nhiên, bây giờ, tôi có thể sử dụng [[:space:]]như trong một trong những câu trả lời khác: stackoverflow.com/a/3352015/3948618
leondepeon

11

Bạn có thể sử dụng trường học cũ tr. Ví dụ, cái này trả về số lượng tệp đã sửa đổi trong kho git, khoảng trắng bị tước.

MYVAR=`git ls-files -m|wc -l|tr -d ' '`

1
Điều này không cắt khoảng trắng từ phía trước và phía sau - nó sẽ loại bỏ tất cả khoảng trắng khỏi chuỗi.
Nick

11

Điều này làm việc cho tôi:

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>

Để đặt nó trên ít dòng hơn cho cùng một kết quả:

text="    trim my edges    "
trimmed=${${text##+( )}%%+( )}

1
Không làm việc cho tôi. Cái đầu tiên in một chuỗi không được đánh giá cao. Thứ hai ném thay thế xấu. Bạn có thể giải thích những gì đang xảy ra ở đây?
musicin3d

1
@ musicin3d: đây là trang web tôi thường xuyên sử dụng để nói về cách thao tác biến hoạt động trong tìm kiếm bash${var##Pattern} để biết thêm chi tiết. Ngoài ra, trang web này giải thích các mẫu bash . Vì vậy, ##phương tiện loại bỏ mẫu đã cho từ phía trước và %%có nghĩa là loại bỏ mẫu đã cho từ phía sau. Các +( )phần là mô hình và nó có nghĩa là "một hoặc xảy ra nhiều hơn một không gian"
gMale

Thật buồn cười, nó đã làm việc trong dấu nhắc, nhưng không phải sau khi chuyển sang tập tin bash.
Bác sĩ Beco

kỳ dị. Đây có phải là phiên bản bash giống nhau trong cả hai trường hợp?
gMale

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

HOẶC LÀ

# 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")")"
}

HOẶC LÀ

# 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]}"
}

HOẶC LÀ

# 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")"
}

HOẶC LÀ

Xây dựng dựa trên linh hồn expr ...

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

HOẶC LÀ

# 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")")"
}

8

Tôi đã thấy các tập lệnh chỉ sử dụng phép gán biến để thực hiện công việc:

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

Khoảng trắng được tự động kết hợp và cắt xén. Người ta phải cẩn thận với các siêu ký tự vỏ (nguy cơ tiêm tiềm năng).

Tôi cũng khuyên bạn nên luôn luôn trích dẫn thay thế hai lần trong điều kiện shell:

if [ -n "$var" ]; then

vì một cái gì đó như -o hoặc nội dung khác trong biến có thể sửa đổi các đối số thử nghiệm của bạn.


3
Đó là việc sử dụng không được trích dẫn $xyzcùng với echođó làm cho khoảng trắng kết hợp lại, không phải là phép gán biến. Để lưu trữ giá trị được cắt trong biến trong ví dụ của bạn, bạn phải sử dụng xyz=$(echo -n $xyz). Ngoài ra, cách tiếp cận này có thể mở rộng tên đường dẫn không mong muốn (globalbing).
mkuity0

Đây là sai, giá trị trong xyzbiến KHÔNG được cắt.
caesarsol

7
var='   a b c   '
trimmed=$(echo $var)

1
Điều đó sẽ không hoạt động nếu có nhiều hơn một khoảng trống ở giữa hai từ bất kỳ. Hãy thử: echo $(echo "1 2 3")(với hai khoảng trắng giữa 1, 2 và 3).
joshlf

7

Tôi chỉ đơn giản là sử dụng sed:

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

a) Ví dụ về cách sử dụng trên chuỗi đơn

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

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

Đầu ra:

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

b) Ví dụ về cách sử dụng trên chuỗi nhiều dòng

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

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

Đầu ra:

GIVEN STRING: |    wordAA
   >wordB<
wordC    |

TRIMMED STRING: |wordAA
   >wordB<
wordC|

c) Lưu ý cuối cùng:
Nếu bạn không muốn sử dụng hàm, đối với chuỗi dòng đơn, bạn chỉ cần sử dụng lệnh "dễ nhớ hơn" như:

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

Thí dụ:

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

Đầu ra:

wordA wordB wordC

Sử dụng ở trên trên các chuỗi nhiều dòng cũng sẽ hoạt động , nhưng xin lưu ý rằng nó cũng sẽ cắt bất kỳ không gian nội bộ kéo dài / hàng đầu nào, như GurM nhận thấy trong các bình luận

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

Đầu ra:

wordAA
>four spaces before<
>one space before<

Vì vậy, nếu bạn có ý định giữ những khoảng trống đó, vui lòng sử dụng chức năng ở đầu câu trả lời của tôi!

d) GIẢI THÍCH cú pháp sed "tìm và thay thế" trên các chuỗi nhiều dòng được sử dụng bên trong hàm 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
}'

Lưu ý: Theo đề xuất của @mkelement, nó sẽ không hoạt động đối với chuỗi nhiều dòng mặc dù nó sẽ hoạt động với các chuỗi đơn.
Giáo sư

1
Bạn đã sai: nó cũng hoạt động trên các chuỗi nhiều dòng. Chỉ cần kiểm tra thôi! :)
Luca Borrione

+1 cho việc sử dụng - giúp tôi dễ dàng kiểm tra mã. Tuy nhiên, mã vẫn không hoạt động đối với các chuỗi nhiều dòng. Nếu bạn nhìn kỹ vào đầu ra, bạn sẽ nhận thấy rằng bất kỳ không gian nội bộ hàng đầu / dấu vết nào cũng sẽ bị xóa, ví dụ, khoảng trắng phía trước "nhiều dòng" được thay thế bằng "nhiều dòng". Chỉ cần thử tăng số lượng khoảng trắng hàng đầu / dấu trên mỗi dòng.
Giáo sư

Bây giờ tôi hiểu ý của bạn! Cảm ơn bạn đã lên đầu, tôi chỉnh sửa câu trả lời của tôi.
Luca Borrione

@ "Luca Borrione" - hoan nghênh :-) Bạn có thể giải thích cú pháp sed bạn đang sử dụng trong trim () không? Nó cũng có thể giúp bất kỳ người dùng mã nào của bạn điều chỉnh nó sang các mục đích sử dụng khác. Ngoài ra, nó thậm chí có thể giúp tìm các trường hợp cạnh cho biểu thức chính quy.
Giáo sư

6

Đây là hàm trim () cắt và chuẩn hóa khoảng trắng

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

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

Và một biến thể khác sử dụng biểu thức thông thường.

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

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

Cách tiếp cận đầu tiên rất khó ở chỗ nó không chỉ bình thường hóa khoảng trắng bên trong (thay thế tất cả các khoảng trắng bên trong bằng một khoảng trắng), mà còn phải tuân theo toàn cầu (mở rộng tên đường dẫn), ví dụ, một *ký tự trong chuỗi đầu vào sẽ mở rộng ra tất cả các tập tin và thư mục trong thư mục làm việc hiện tại. Cuối cùng, nếu $ IFS được đặt thành giá trị không mặc định, việc cắt xén có thể không hoạt động (mặc dù điều đó dễ dàng được khắc phục bằng cách thêm local IFS=$' \t\n'). Việc cắt xén được giới hạn trong các hình thức khoảng trắng sau: khoảng trắng \t\nký tự.
mkuity0

1
Cách tiếp cận dựa trên biểu thức chính quy thứ hai là tuyệt vời và không có tác dụng phụ, nhưng ở dạng hiện tại của nó có vấn đề: (a) trên bash v3.2 +, theo mặc định sẽ không hoạt động, vì biểu thức chính quy phải được bỏ qua theo thứ tự để hoạt động và (b) chính biểu thức chính quy không xử lý trường hợp trong đó chuỗi đầu vào là một ký tự không phải một khoảng trắng được bao quanh bởi các khoảng trắng. Để khắc phục những sự cố này, thay thế ifdòng bằng : if [[ "$trimmed" =~ ' '*([^ ]|[^ ].*[^ ])' '* ]]. Cuối cùng, cách tiếp cận chỉ đề cập đến các khoảng trắng, không phải các dạng khoảng trắng khác (xem bình luận tiếp theo của tôi).
mkuity0

2
Hàm sử dụng biểu thức thông thường chỉ xử lý các khoảng trắng chứ không phải các dạng khoảng trắng khác, nhưng thật dễ dàng để khái quát hóa: Thay thế ifdòng bằng:[[ "$trimmed" =~ [[:space:]]*([^[:space:]]|[^[:space:]].*[^[:space:]])[[:space:]]* ]]
mkuity0

6

Sử dụng AWK:

echo $var | awk '{gsub(/^ +| +$/,"")}1'

Ngọt ngào mà dường như làm việc (ví dụ :) $stripped_version=echo $ var | awk '{gsub (/ ^ + | + $ /, "")} 1'``
rogerdpack

4
ngoại trừ awk không làm gì cả: tiếng vang của một biến không được trích dẫn đã loại bỏ khoảng trắng
glenn jackman

6

Bài tập bỏ qua khoảng trắng hàng đầu và dấu và như vậy có thể được sử dụng để cắt:

$ var=`echo '   hello'`; echo $var
hello

8
Đo không phải sự thật. Đó là "echo" loại bỏ khoảng trắng, không phải bài tập. Trong ví dụ của bạn, làm echo "$var"để xem giá trị với khoảng trắng.
Nicholas Sushkin

2
@NicholasSushkin Một người có thể làm var=$(echo $var)nhưng tôi không khuyên bạn nên làm điều đó. Các giải pháp khác được trình bày ở đây được ưa thích.
xebeche

5

Điều này không có vấn đề với việc tạo khối không mong muốn, ngoài ra, không gian trắng bên trong không được sửa đổi (giả sử $IFSđược đặt thành mặc định, đó là ' \t\n').

Nó đọc đến dòng mới đầu tiên (và không bao gồm nó) hoặc phần cuối của chuỗi, tùy theo điều kiện nào đến trước và loại bỏ mọi kết hợp giữa không gian và \tký tự hàng đầu và dấu . Nếu bạn muốn duy trì nhiều dòng (và cả dải dòng mới và hàng đầu), read -r -d '' var << eofthay vào đó , hãy sử dụng ; tuy nhiên, lưu ý rằng nếu đầu vào của bạn có chứa \neof, nó sẽ bị cắt ngay trước đó. (Các dạng khác của khoảng trắng, cụ thể là \r, \f\v, không bị tước, ngay cả khi bạn thêm chúng vào $ IFS.)

read -r var << eof
$var
eof


5

Điều này sẽ xóa tất cả các khoảng trắng khỏi Chuỗi của bạn,

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

/thay thế lần xuất hiện đầu tiên và //tất cả sự xuất hiện của khoảng trắng trong chuỗi. Tức là tất cả các khoảng trắng được thay thế bởi - không có gì


4

Đây là phương pháp đơn giản nhất tôi từng thấy. Nó chỉ sử dụng Bash, nó chỉ có một vài dòng, biểu thức chính là đơn giản và phù hợp với tất cả các dạng khoảng trắng:

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

Đây là một kịch bản mẫu để kiểm tra nó với:

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."

1
Chắc chắn là thích hơn, ví dụ (các vị thần!), Tách ra cho Python. Ngoại trừ tôi nghĩ đơn giản và tổng quát hơn để xử lý chính xác chuỗi chỉ chứa khoảng trắng. Biểu thức được đơn giản hóa nhẹ sẽ là:^[[:space:]]*(.*[^[:space:]])?[[:space:]]*$
Ron Burk

4

Python có một chức năng strip()hoạt động giống hệt với PHP trim(), vì vậy chúng ta chỉ cần thực hiện một Python nhỏ nội tuyến để tạo ra một tiện ích dễ hiểu cho việc này:

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

Điều này sẽ cắt khoảng trắng hàng đầu và dấu (bao gồm cả dòng mới).

$ x=`echo -e "\n\t   \n" | trim`
$ if [ -z "$x" ]; then echo hi; fi
hi

trong khi nó hoạt động, bạn có thể muốn xem xét việc cung cấp một giải pháp không liên quan đến việc khởi chạy một trình thông dịch python đầy đủ chỉ để cắt một chuỗi. Thật lãng phí.
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]
#[]

3

Tôi thấy rằng tôi cần thêm một số mã từ một sdiffđầu ra lộn xộn để dọn sạch nó:

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'

Điều này loại bỏ các dấu cách và các ký tự vô hình khác.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.